mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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 <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
d5764b3ee8
commit
45634ed2da
25 changed files with 534 additions and 118 deletions
|
@ -11,13 +11,19 @@ Check the status of your cluster.
|
|||
[[upgrade-assistant-api-status-request]]
|
||||
==== Request
|
||||
|
||||
`GET <kibana host>:<port>/api/upgrade_assistant/status`
|
||||
`GET <kibana host>:<port>/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."
|
||||
}
|
||||
--------------------------------------------------
|
||||
|
|
|
@ -16,7 +16,7 @@ describe('Cluster settings deprecation flyout', () => {
|
|||
let testBed: ElasticsearchTestBed;
|
||||
let httpRequestsMockHelpers: ReturnType<typeof setupEnvironment>['httpRequestsMockHelpers'];
|
||||
let httpSetup: ReturnType<typeof setupEnvironment>['httpSetup'];
|
||||
const clusterSettingDeprecation = esDeprecationsMockResponse.deprecations[4];
|
||||
const clusterSettingDeprecation = esDeprecationsMockResponse.migrationsDeprecations[4];
|
||||
|
||||
beforeEach(async () => {
|
||||
const mockEnvironment = setupEnvironment();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('Index settings deprecation flyout', () => {
|
|||
let testBed: ElasticsearchTestBed;
|
||||
let httpRequestsMockHelpers: ReturnType<typeof setupEnvironment>['httpRequestsMockHelpers'];
|
||||
let httpSetup: ReturnType<typeof setupEnvironment>['httpSetup'];
|
||||
const indexSettingDeprecation = esDeprecationsMockResponse.deprecations[1];
|
||||
const indexSettingDeprecation = esDeprecationsMockResponse.migrationsDeprecations[1];
|
||||
beforeEach(async () => {
|
||||
const mockEnvironment = setupEnvironment();
|
||||
httpRequestsMockHelpers = mockEnvironment.httpRequestsMockHelpers;
|
||||
|
|
|
@ -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<typeof setupEnvironment>['httpRequestsMockHelpers'];
|
||||
let httpSetup: ReturnType<typeof setupEnvironment>['httpSetup'];
|
||||
beforeEach(async () => {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: [],
|
||||
};
|
||||
|
|
|
@ -244,7 +244,9 @@ export interface CloudBackupStatus {
|
|||
|
||||
export interface ESUpgradeStatus {
|
||||
totalCriticalDeprecations: number;
|
||||
deprecations: EnrichedDeprecationInfo[];
|
||||
migrationsDeprecations: EnrichedDeprecationInfo[];
|
||||
totalCriticalHealthIssues: number;
|
||||
enrichedHealthIndicators: EnrichedDeprecationInfo[];
|
||||
}
|
||||
|
||||
export interface ResolveIndexResponseFromES {
|
||||
|
|
|
@ -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 <SectionLoading>{i18nTexts.isLoading}</SectionLoading>;
|
||||
}
|
||||
|
||||
if (esDeprecations?.deprecations?.length === 0) {
|
||||
if (esDeprecations?.migrationsDeprecations?.length === 0) {
|
||||
return (
|
||||
<NoDeprecationsPrompt
|
||||
deprecationType="Elasticsearch"
|
||||
|
@ -198,7 +198,10 @@ export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => {
|
|||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<EsDeprecationsTable deprecations={esDeprecations?.deprecations} reload={resendRequest} />
|
||||
<EsDeprecationsTable
|
||||
deprecations={esDeprecations?.migrationsDeprecations}
|
||||
reload={resendRequest}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -23,11 +23,13 @@ export const EsDeprecationIssuesPanel: FunctionComponent<Props> = ({ 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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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)!);
|
||||
};
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}"`
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<FeatureSet> = {}) => {
|
||||
const mockRouter = createMockRouter();
|
||||
const routeDependencies: any = {
|
||||
config: {
|
||||
featureSet: {
|
||||
mlSnapshots: true,
|
||||
migrateSystemIndices: true,
|
||||
reindexCorrectiveActions: true,
|
||||
...featureSetOverrides,
|
||||
beforeAll(() => {
|
||||
versionService.setup('8.17.0');
|
||||
});
|
||||
|
||||
describe('GET /api/upgrade_assistant/status for major upgrade', () => {
|
||||
const registerRoutes = (featureSetOverrides: Partial<FeatureSet> = {}) => {
|
||||
const mockRouter = createMockRouter();
|
||||
const routeDependencies: any = {
|
||||
config: {
|
||||
featureSet: {
|
||||
mlSnapshots: true,
|
||||
migrateSystemIndices: true,
|
||||
reindexCorrectiveActions: true,
|
||||
...featureSetOverrides,
|
||||
},
|
||||
},
|
||||
},
|
||||
router: mockRouter,
|
||||
lib: { handleEsError },
|
||||
router: mockRouter,
|
||||
lib: { handleEsError },
|
||||
current: currentVersion,
|
||||
defaultTarget: nextMajor,
|
||||
};
|
||||
|
||||
registerUpgradeStatusRoute(routeDependencies);
|
||||
|
||||
return { mockRouter, routeDependencies };
|
||||
};
|
||||
|
||||
registerUpgradeStatusRoute(routeDependencies);
|
||||
|
||||
return { mockRouter, routeDependencies };
|
||||
};
|
||||
|
||||
describe('GET /api/upgrade_assistant/status', () => {
|
||||
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<FeatureSet> = {}) => {
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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}.',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue