mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[UA] Support Deprecated Data Streams Migrations (#202204)](https://github.com/elastic/kibana/pull/202204) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Ahmad Bamieh","email":"ahmad.bamyeh@elastic.co"},"sourceCommit":{"committedDate":"2024-12-02T21:53:24Z","message":"[UA] Support Deprecated Data Streams Migrations (#202204)\n\n## Summary\r\n\r\n- [x] Fix UA currently failing to return upgrade status\r\n- [x] Support surfacing `data_streams` migrations in UA under the ES tab\r\n- [x] Refactor code for better readablity\r\n- [x] Add more test cases across the board for all the es migrations\r\nstatus feature in UA\r\n- [x] Add a `featureSet.migrateDataStreams` to enable surfacing data\r\nstreams migrations\r\n- [x] Surface data streams in UA UI\r\n- [x] Take screenshots for a product review discussions\r\n- [x] Unskip api_integration test cases\r\n\r\n### Imporant Notes\r\n\r\nES deprecations are hidden behind the `featureSet` flag and will only be\r\nshown in `8.last` for users.\r\nThis gives us time to review the copy and implement the corrective\r\naction for reindexing data streams which is still pending implementaiton\r\nfrom ES side.\r\n\r\nFor now we will merge this to unblock upgrades in `8.17` and support\r\nsurfacing data_streams deprecations and add tests.\r\n\r\nFollow up work for `8.18`\r\n- Add integration Tests\r\n- Update copy of flyout and documentation link\r\n- Reindexing data streams corrective action\r\n\r\ncloses https://github.com/elastic/kibana-team/issues/1293\r\n\r\n## Screenshots\r\n\r\n#### Overview Page\r\n<img width=\"683\" alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/246d89ac-02cd-4813-ba38-e2e28df00c8d\">\r\n\r\n#### Elasticsearch deprecation issues Page\r\n\r\n<img width=\"1453\" alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/b5fd5f15-fa44-4acb-b7ff-4973593dcfbb\">\r\n\r\n\r\n#### Data streams deprecation details flyout\r\n<img width=\"778\" alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/af343f69-7e76-4c91-a6e3-cff29e26df59\">\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"a6b3743e00add07c315eacadda4d0dbb532042ac","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","auto-backport","backport:prev-minor","backport:version","v8.17.0","v8.18.0"],"title":"[UA] Support Deprecated Data Streams Migrations","number":202204,"url":"https://github.com/elastic/kibana/pull/202204","mergeCommit":{"message":"[UA] Support Deprecated Data Streams Migrations (#202204)\n\n## Summary\r\n\r\n- [x] Fix UA currently failing to return upgrade status\r\n- [x] Support surfacing `data_streams` migrations in UA under the ES tab\r\n- [x] Refactor code for better readablity\r\n- [x] Add more test cases across the board for all the es migrations\r\nstatus feature in UA\r\n- [x] Add a `featureSet.migrateDataStreams` to enable surfacing data\r\nstreams migrations\r\n- [x] Surface data streams in UA UI\r\n- [x] Take screenshots for a product review discussions\r\n- [x] Unskip api_integration test cases\r\n\r\n### Imporant Notes\r\n\r\nES deprecations are hidden behind the `featureSet` flag and will only be\r\nshown in `8.last` for users.\r\nThis gives us time to review the copy and implement the corrective\r\naction for reindexing data streams which is still pending implementaiton\r\nfrom ES side.\r\n\r\nFor now we will merge this to unblock upgrades in `8.17` and support\r\nsurfacing data_streams deprecations and add tests.\r\n\r\nFollow up work for `8.18`\r\n- Add integration Tests\r\n- Update copy of flyout and documentation link\r\n- Reindexing data streams corrective action\r\n\r\ncloses https://github.com/elastic/kibana-team/issues/1293\r\n\r\n## Screenshots\r\n\r\n#### Overview Page\r\n<img width=\"683\" alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/246d89ac-02cd-4813-ba38-e2e28df00c8d\">\r\n\r\n#### Elasticsearch deprecation issues Page\r\n\r\n<img width=\"1453\" alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/b5fd5f15-fa44-4acb-b7ff-4973593dcfbb\">\r\n\r\n\r\n#### Data streams deprecation details flyout\r\n<img width=\"778\" alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/af343f69-7e76-4c91-a6e3-cff29e26df59\">\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"a6b3743e00add07c315eacadda4d0dbb532042ac"}},"sourceBranch":"main","suggestedTargetBranches":["8.17","8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/202204","number":202204,"mergeCommit":{"message":"[UA] Support Deprecated Data Streams Migrations (#202204)\n\n## Summary\r\n\r\n- [x] Fix UA currently failing to return upgrade status\r\n- [x] Support surfacing `data_streams` migrations in UA under the ES tab\r\n- [x] Refactor code for better readablity\r\n- [x] Add more test cases across the board for all the es migrations\r\nstatus feature in UA\r\n- [x] Add a `featureSet.migrateDataStreams` to enable surfacing data\r\nstreams migrations\r\n- [x] Surface data streams in UA UI\r\n- [x] Take screenshots for a product review discussions\r\n- [x] Unskip api_integration test cases\r\n\r\n### Imporant Notes\r\n\r\nES deprecations are hidden behind the `featureSet` flag and will only be\r\nshown in `8.last` for users.\r\nThis gives us time to review the copy and implement the corrective\r\naction for reindexing data streams which is still pending implementaiton\r\nfrom ES side.\r\n\r\nFor now we will merge this to unblock upgrades in `8.17` and support\r\nsurfacing data_streams deprecations and add tests.\r\n\r\nFollow up work for `8.18`\r\n- Add integration Tests\r\n- Update copy of flyout and documentation link\r\n- Reindexing data streams corrective action\r\n\r\ncloses https://github.com/elastic/kibana-team/issues/1293\r\n\r\n## Screenshots\r\n\r\n#### Overview Page\r\n<img width=\"683\" alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/246d89ac-02cd-4813-ba38-e2e28df00c8d\">\r\n\r\n#### Elasticsearch deprecation issues Page\r\n\r\n<img width=\"1453\" alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/b5fd5f15-fa44-4acb-b7ff-4973593dcfbb\">\r\n\r\n\r\n#### Data streams deprecation details flyout\r\n<img width=\"778\" alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/af343f69-7e76-4c91-a6e3-cff29e26df59\">\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"a6b3743e00add07c315eacadda4d0dbb532042ac"}},{"branch":"8.17","label":"v8.17.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.x","label":"v8.18.0","branchLabelMappingKey":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Ahmad Bamieh <ahmad.bamyeh@elastic.co>
This commit is contained in:
parent
bc826427c8
commit
53e01df17c
19 changed files with 743 additions and 502 deletions
|
@ -354,6 +354,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
'xpack.upgrade_assistant.featureSet.migrateSystemIndices (boolean?)',
|
||||
'xpack.upgrade_assistant.featureSet.mlSnapshots (boolean?)',
|
||||
'xpack.upgrade_assistant.featureSet.reindexCorrectiveActions (boolean?)',
|
||||
'xpack.upgrade_assistant.featureSet.migrateDataStreams (boolean?)',
|
||||
'xpack.upgrade_assistant.ui.enabled (boolean?)',
|
||||
'xpack.observability.unsafe.alertDetails.metrics.enabled (boolean?)',
|
||||
'xpack.observability.unsafe.alertDetails.logs.enabled (boolean?)',
|
||||
|
|
|
@ -15,6 +15,7 @@ Some features of the UA are only needed when upgrading to a new major version. T
|
|||
* ML Snapshots (`featureSet.mlSnapshots`): Machine learning Upgrade mode can be toggled from outside Kibana, the purpose of this feature guard is to hide all ML related deprecations from the end user until the next major upgrade.
|
||||
When we want to enable ML model snapshot deprecation warnings again we need to change the constant `MachineLearningField.MIN_CHECKED_SUPPORTED_SNAPSHOT_VERSION` to something higher than 7.0.0 in the Elasticsearch code.
|
||||
* Migrating system indices (`featureSet.migrateSystemIndices`): Migrating system indices should only be enabled for major version upgrades. This config hides the second step from the UA UI for migrating system indices.
|
||||
* Reindex Data Streams (`featureSet.migrateDataStreams`): Migrating deprecated Data streams should only be enabled for major version upgrades. The purpose of this feature guard is to hide all data streams related deprecations from the end user until the next major upgrade.
|
||||
* Reindex corrective actions (`featureSet.reindexCorrectiveActions`): Deprecations with reindexing corrective actions are only enabled for major version upgrades. Currently, the reindex actions include some logic that is specific to the [8.0 upgrade](https://github.com/elastic/kibana/blob/main/x-pack/plugins/upgrade_assistant/server/lib/reindexing/index_settings.ts). End users could get into a bad situation if this is enabled before this logic is fixed.
|
||||
|
||||
## Deprecations
|
||||
|
|
|
@ -80,6 +80,7 @@ export const getAppContextMock = (kibanaVersion: SemVer) => ({
|
|||
featureSet: {
|
||||
mlSnapshots: true,
|
||||
migrateSystemIndices: true,
|
||||
migrateDataStreams: true,
|
||||
reindexCorrectiveActions: true,
|
||||
},
|
||||
kibanaVersionInfo: {
|
||||
|
|
|
@ -215,7 +215,7 @@ export interface HealthIndicatorAction {
|
|||
|
||||
export interface EnrichedDeprecationInfo
|
||||
extends Omit<estypes.MigrationDeprecationsDeprecation, 'level'> {
|
||||
type: keyof estypes.MigrationDeprecationsResponse | 'health_indicator';
|
||||
type: keyof estypes.MigrationDeprecationsResponse | 'health_indicator' | 'data_streams';
|
||||
isCritical: boolean;
|
||||
status?: estypes.HealthReportIndicatorHealthStatus;
|
||||
index?: string;
|
||||
|
@ -296,4 +296,5 @@ export interface FeatureSet {
|
|||
migrateSystemIndices: boolean;
|
||||
mlSnapshots: boolean;
|
||||
reindexCorrectiveActions: boolean;
|
||||
migrateDataStreams: boolean;
|
||||
}
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EnrichedDeprecationInfo } from '../../../common/types';
|
||||
|
||||
export const DEPRECATION_TYPE_MAP = {
|
||||
export const DEPRECATION_TYPE_MAP: Record<EnrichedDeprecationInfo['type'], string> = {
|
||||
cluster_settings: i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecations.clusterDeprecationTypeLabel',
|
||||
{
|
||||
|
@ -32,6 +33,9 @@ export const DEPRECATION_TYPE_MAP = {
|
|||
defaultMessage: 'Health Indicator',
|
||||
}
|
||||
),
|
||||
data_streams: i18n.translate('xpack.upgradeAssistant.esDeprecations.dataStreamsTypeLabel', {
|
||||
defaultMessage: 'Data Stream',
|
||||
}),
|
||||
};
|
||||
|
||||
export const PAGINATION_CONFIG = {
|
||||
|
|
|
@ -47,6 +47,11 @@ const configSchema = schema.object({
|
|||
* End users could get into a bad situation if this is enabled before this logic is fixed.
|
||||
*/
|
||||
reindexCorrectiveActions: schema.boolean({ defaultValue: false }),
|
||||
/**
|
||||
* Migrating deprecated data streams should only be enabled for major version upgrades.
|
||||
* Currently this is manually set to `true` on every `x.last` version.
|
||||
*/
|
||||
migrateDataStreams: schema.boolean({ defaultValue: false }),
|
||||
}),
|
||||
/**
|
||||
* This config allows to hide the UI without disabling the plugin.
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const getMockEsDeprecations = () => {
|
||||
return {
|
||||
cluster_settings: [],
|
||||
node_settings: [],
|
||||
ml_settings: [],
|
||||
index_settings: {},
|
||||
data_streams: {},
|
||||
};
|
||||
};
|
||||
|
||||
export const getMockMlSettingsDeprecations = () => {
|
||||
return {
|
||||
ml_settings: [
|
||||
{
|
||||
level: 'warning',
|
||||
message: 'Datafeed [deprecation-datafeed] uses deprecated query options',
|
||||
url: 'https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-7.0.html#breaking_70_search_changes',
|
||||
details:
|
||||
'[Deprecated field [use_dis_max] used, replaced by [Set [tie_breaker] to 1 instead]]',
|
||||
// @ts-ignore
|
||||
resolve_during_rolling_upgrade: false,
|
||||
},
|
||||
{
|
||||
level: 'critical',
|
||||
message:
|
||||
'model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded',
|
||||
url: '',
|
||||
details: 'details',
|
||||
// @ts-ignore
|
||||
_meta: { snapshot_id: '1', job_id: 'deprecation_check_job' },
|
||||
// @ts-ignore
|
||||
resolve_during_rolling_upgrade: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export const getMockDataStreamDeprecations = () => {
|
||||
return {
|
||||
data_streams: {
|
||||
'my-v7-data-stream': [
|
||||
{
|
||||
level: 'critical',
|
||||
message: 'Old data stream with a compatibility version < 8.0',
|
||||
url: 'https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-9.0.html',
|
||||
details:
|
||||
'This data stream has backing indices that were created before Elasticsearch 8.0.0',
|
||||
resolve_during_rolling_upgrade: false,
|
||||
_meta: {
|
||||
backing_indices: {
|
||||
count: 52,
|
||||
need_upgrading: {
|
||||
count: 37,
|
||||
searchable_snapshot: {
|
||||
count: 23,
|
||||
fully_mounted: {
|
||||
count: 7,
|
||||
},
|
||||
partially_mounted: {
|
||||
count: 16,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
|
@ -148,5 +148,31 @@
|
|||
"resolve_during_rolling_upgrade": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"data_streams": {
|
||||
"my-v7-data-stream" : [{
|
||||
"level" : "critical",
|
||||
"message" : "Old data stream with a compatibility version < 8.0",
|
||||
"url" : "https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-9.0.html",
|
||||
"details" : "This data stream has backing indices that were created before Elasticsearch 8.0.0",
|
||||
"resolve_during_rolling_upgrade" : false,
|
||||
"_meta": {
|
||||
"backing_indices": {
|
||||
"count": 52,
|
||||
"need_upgrading": {
|
||||
"count": 37,
|
||||
"searchable_snapshot": {
|
||||
"count": 23,
|
||||
"fully_mounted": {
|
||||
"count": 7
|
||||
},
|
||||
"partially_mounted": {
|
||||
"count": 16
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,340 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EnrichedDeprecationInfo, ESUpgradeStatus, FeatureSet } from '../../common/types';
|
||||
import { esIndicesStateCheck } from './es_indices_state_check';
|
||||
import {
|
||||
getESSystemIndicesMigrationStatus,
|
||||
convertFeaturesToIndicesArray,
|
||||
} from './es_system_indices_migration';
|
||||
|
||||
export function getShardCapacityDeprecationInfo({
|
||||
symptom,
|
||||
details,
|
||||
}: {
|
||||
details: any;
|
||||
symptom: any;
|
||||
}) {
|
||||
// When we dont have a details field for our indicator, we can only report
|
||||
// the symptom to the user given that's the only information about the deprecation
|
||||
// we have.
|
||||
if (!details) {
|
||||
return {
|
||||
details: symptom,
|
||||
message: symptom,
|
||||
url: null,
|
||||
resolveDuringUpgrade: false,
|
||||
};
|
||||
}
|
||||
|
||||
const causes = [];
|
||||
if (details.indices_with_readonly_block > 0) {
|
||||
causes.push(
|
||||
i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecationsStatus.indicesWithReadonlyBlockCauseMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'The number of indices the system enforced a read-only index block (`index.blocks.read_only_allow_delete`) on because the cluster is running out of space.',
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (details.nodes_over_high_watermark > 0) {
|
||||
causes.push(
|
||||
i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecationsStatus.nodesOverHighWatermarkCauseMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'The number of nodes that are running low on disk and it is likely that they will run out of space. Their disk usage has tripped the <<cluster-routing-watermark-high, high watermark threshold>>.',
|
||||
ignoreTag: true,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (details.nodes_over_flood_stage_watermark > 0) {
|
||||
causes.push(
|
||||
i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecationsStatus.nodesOverFloodStageWatermarkCauseMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'The number of nodes that have run out of disk. Their disk usage has tripped the <<cluster-routing-flood-stage, flood stagewatermark threshold>>.',
|
||||
ignoreTag: true,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
details: symptom,
|
||||
message: symptom,
|
||||
url: null,
|
||||
resolveDuringUpgrade: false,
|
||||
correctiveAction: {
|
||||
type: 'healthIndicator',
|
||||
impacts: details,
|
||||
cause: causes.join('\n'),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function getHealthIndicators(
|
||||
dataClient: IScopedClusterClient
|
||||
): Promise<EnrichedDeprecationInfo[]> {
|
||||
const healthIndicators = await dataClient.asCurrentUser.healthReport();
|
||||
const isStatusNotGreen = (indicator?: estypes.HealthReportBaseIndicator): boolean => {
|
||||
return !!(indicator?.status && indicator?.status !== 'green');
|
||||
};
|
||||
|
||||
// Temporarily ignoring due to untyped ES indicators
|
||||
// types will be available during 8.9.0
|
||||
// @ts-ignore
|
||||
return [
|
||||
...[
|
||||
// @ts-ignore
|
||||
healthIndicators.indicators.shards_capacity as estypes.HealthReportBaseIndicator,
|
||||
]
|
||||
.filter(isStatusNotGreen)
|
||||
.flatMap(({ status, symptom, impacts, diagnosis }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
return (diagnosis || []).map(({ cause, action, help_url }) => ({
|
||||
type: 'health_indicator',
|
||||
details: symptom,
|
||||
message: cause,
|
||||
url: help_url,
|
||||
isCritical: status === 'red',
|
||||
resolveDuringUpgrade: false,
|
||||
correctiveAction: { type: 'healthIndicator', cause, action, impacts },
|
||||
}));
|
||||
}),
|
||||
...[healthIndicators.indicators.disk as estypes.HealthReportDiskIndicator]
|
||||
.filter(isStatusNotGreen)
|
||||
.flatMap(({ status, symptom, details }) => {
|
||||
return {
|
||||
type: 'health_indicator',
|
||||
isCritical: status === 'red',
|
||||
...getShardCapacityDeprecationInfo({ symptom, details }),
|
||||
};
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
export async function getESUpgradeStatus(
|
||||
dataClient: IScopedClusterClient,
|
||||
featureSet: FeatureSet
|
||||
): Promise<ESUpgradeStatus> {
|
||||
const getCombinedDeprecations = async () => {
|
||||
const healthIndicators = await getHealthIndicators(dataClient);
|
||||
const deprecations = await dataClient.asCurrentUser.migration.deprecations();
|
||||
const indices = await getCombinedIndexInfos(deprecations, dataClient);
|
||||
const systemIndices = await getESSystemIndicesMigrationStatus(dataClient.asCurrentUser);
|
||||
const systemIndicesList = convertFeaturesToIndicesArray(systemIndices.features);
|
||||
|
||||
const enrichedDeprecations = Object.keys(deprecations).reduce(
|
||||
(combinedDeprecations, deprecationType) => {
|
||||
if (deprecationType === 'index_settings') {
|
||||
// We need to exclude all index related deprecations for system indices since
|
||||
// they are resolved separately through the system indices upgrade section in
|
||||
// the Overview page.
|
||||
const withoutSystemIndices = indices.filter(
|
||||
(index) => !systemIndicesList.includes(index.index!)
|
||||
);
|
||||
|
||||
combinedDeprecations = combinedDeprecations.concat(withoutSystemIndices);
|
||||
} else {
|
||||
const deprecationsByType = deprecations[
|
||||
deprecationType as keyof estypes.MigrationDeprecationsResponse
|
||||
] as estypes.MigrationDeprecationsDeprecation[];
|
||||
|
||||
const enrichedDeprecationInfo = deprecationsByType
|
||||
.map(
|
||||
({
|
||||
details,
|
||||
level,
|
||||
message,
|
||||
url,
|
||||
// @ts-expect-error @elastic/elasticsearch _meta not available yet in MigrationDeprecationInfoResponse
|
||||
_meta: metadata,
|
||||
// @ts-expect-error @elastic/elasticsearch resolve_during_rolling_upgrade not available yet in MigrationDeprecationInfoResponse
|
||||
resolve_during_rolling_upgrade: resolveDuringUpgrade,
|
||||
}) => {
|
||||
return {
|
||||
details,
|
||||
message,
|
||||
url,
|
||||
type: deprecationType as keyof estypes.MigrationDeprecationsResponse,
|
||||
isCritical: level === 'critical',
|
||||
resolveDuringUpgrade,
|
||||
correctiveAction: getCorrectiveAction(message, metadata),
|
||||
};
|
||||
}
|
||||
)
|
||||
.filter(({ correctiveAction, type }) => {
|
||||
/**
|
||||
* This disables showing the ML deprecations in the UA if `featureSet.mlSnapshots`
|
||||
* is set to `false`.
|
||||
*
|
||||
* This config should be set to true only on the `x.last` versions, or when
|
||||
* the constant `MachineLearningField.MIN_CHECKED_SUPPORTED_SNAPSHOT_VERSION`
|
||||
* is incremented to something higher than 7.0.0 in the Elasticsearch code.
|
||||
*/
|
||||
if (!featureSet.mlSnapshots) {
|
||||
if (type === 'ml_settings' || correctiveAction?.type === 'mlSnapshot') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This disables showing the reindexing deprecations in the UA if
|
||||
* `featureSet.reindexCorrectiveActions` is set to `false`.
|
||||
*/
|
||||
if (!featureSet.reindexCorrectiveActions && correctiveAction?.type === 'reindex') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
combinedDeprecations = combinedDeprecations.concat(enrichedDeprecationInfo);
|
||||
}
|
||||
|
||||
return combinedDeprecations;
|
||||
},
|
||||
[] as EnrichedDeprecationInfo[]
|
||||
);
|
||||
|
||||
const enrichedHealthIndicators = healthIndicators.filter(({ status }) => {
|
||||
return status !== 'green';
|
||||
}) as EnrichedDeprecationInfo[];
|
||||
|
||||
return [...enrichedHealthIndicators, ...enrichedDeprecations];
|
||||
};
|
||||
|
||||
const combinedDeprecations = await getCombinedDeprecations();
|
||||
const criticalWarnings = combinedDeprecations.filter(({ isCritical }) => isCritical === true);
|
||||
|
||||
return {
|
||||
totalCriticalDeprecations: criticalWarnings.length,
|
||||
deprecations: combinedDeprecations,
|
||||
};
|
||||
}
|
||||
|
||||
// Reformats the index deprecations to an array of deprecation warnings extended with an index field.
|
||||
const getCombinedIndexInfos = async (
|
||||
deprecations: estypes.MigrationDeprecationsResponse,
|
||||
dataClient: IScopedClusterClient
|
||||
) => {
|
||||
const indices = Object.keys(deprecations.index_settings).reduce(
|
||||
(indexDeprecations, indexName) => {
|
||||
return indexDeprecations.concat(
|
||||
deprecations.index_settings[indexName].map(
|
||||
({
|
||||
details,
|
||||
message,
|
||||
url,
|
||||
level,
|
||||
// @ts-expect-error @elastic/elasticsearch _meta not available yet in MigrationDeprecationInfoResponse
|
||||
_meta: metadata,
|
||||
// @ts-expect-error @elastic/elasticsearch resolve_during_rolling_upgrade not available yet in MigrationDeprecationInfoResponse
|
||||
resolve_during_rolling_upgrade: resolveDuringUpgrade,
|
||||
}) =>
|
||||
({
|
||||
details,
|
||||
message,
|
||||
url,
|
||||
index: indexName,
|
||||
type: 'index_settings',
|
||||
isCritical: level === 'critical',
|
||||
correctiveAction: getCorrectiveAction(message, metadata, indexName),
|
||||
resolveDuringUpgrade,
|
||||
} as EnrichedDeprecationInfo)
|
||||
)
|
||||
);
|
||||
},
|
||||
[] as EnrichedDeprecationInfo[]
|
||||
);
|
||||
|
||||
const indexNames = indices.map(({ index }) => index!);
|
||||
|
||||
// If we have found deprecation information for index/indices
|
||||
// check whether the index is open or closed.
|
||||
if (indexNames.length) {
|
||||
const indexStates = await esIndicesStateCheck(dataClient.asCurrentUser, indexNames);
|
||||
|
||||
indices.forEach((indexData) => {
|
||||
if (indexData.correctiveAction?.type === 'reindex') {
|
||||
indexData.correctiveAction.blockerForReindexing =
|
||||
indexStates[indexData.index!] === 'closed' ? 'index-closed' : undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
return indices as EnrichedDeprecationInfo[];
|
||||
};
|
||||
|
||||
interface Action {
|
||||
action_type: 'remove_settings';
|
||||
objects: string[];
|
||||
}
|
||||
|
||||
interface Actions {
|
||||
actions: Action[];
|
||||
}
|
||||
|
||||
type EsMetadata = Actions & {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
const getCorrectiveAction = (
|
||||
message: string,
|
||||
metadata: EsMetadata,
|
||||
indexName?: string
|
||||
): EnrichedDeprecationInfo['correctiveAction'] => {
|
||||
const indexSettingDeprecation = metadata?.actions?.find(
|
||||
(action) => action.action_type === 'remove_settings' && indexName
|
||||
);
|
||||
const clusterSettingDeprecation = metadata?.actions?.find(
|
||||
(action) => action.action_type === 'remove_settings' && typeof indexName === 'undefined'
|
||||
);
|
||||
const requiresReindexAction = /Index created before/.test(message);
|
||||
const requiresIndexSettingsAction = Boolean(indexSettingDeprecation);
|
||||
const requiresClusterSettingsAction = Boolean(clusterSettingDeprecation);
|
||||
const requiresMlAction = /[Mm]odel snapshot/.test(message);
|
||||
|
||||
if (requiresReindexAction) {
|
||||
return {
|
||||
type: 'reindex',
|
||||
};
|
||||
}
|
||||
|
||||
if (requiresIndexSettingsAction) {
|
||||
return {
|
||||
type: 'indexSetting',
|
||||
deprecatedSettings: indexSettingDeprecation!.objects,
|
||||
};
|
||||
}
|
||||
|
||||
if (requiresClusterSettingsAction) {
|
||||
return {
|
||||
type: 'clusterSetting',
|
||||
deprecatedSettings: clusterSettingDeprecation!.objects,
|
||||
};
|
||||
}
|
||||
|
||||
if (requiresMlAction) {
|
||||
const { snapshot_id: snapshotId, job_id: jobId } = metadata!;
|
||||
|
||||
return {
|
||||
type: 'mlSnapshot',
|
||||
snapshotId,
|
||||
jobId,
|
||||
};
|
||||
}
|
||||
};
|
|
@ -6,6 +6,7 @@ Object {
|
|||
Object {
|
||||
"correctiveAction": undefined,
|
||||
"details": "templates using \`template\` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template",
|
||||
"index": undefined,
|
||||
"isCritical": false,
|
||||
"message": "Template patterns are no longer using \`template\` field, but \`index_patterns\` instead",
|
||||
"resolveDuringUpgrade": false,
|
||||
|
@ -15,6 +16,7 @@ Object {
|
|||
Object {
|
||||
"correctiveAction": undefined,
|
||||
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}",
|
||||
"index": undefined,
|
||||
"isCritical": false,
|
||||
"message": "one or more templates use deprecated mapping settings",
|
||||
"resolveDuringUpgrade": false,
|
||||
|
@ -24,6 +26,7 @@ Object {
|
|||
Object {
|
||||
"correctiveAction": undefined,
|
||||
"details": "[Deprecated field [use_dis_max] used, replaced by [Set [tie_breaker] to 1 instead]]",
|
||||
"index": undefined,
|
||||
"isCritical": false,
|
||||
"message": "Datafeed [deprecation-datafeed] uses deprecated query options",
|
||||
"resolveDuringUpgrade": false,
|
||||
|
@ -37,6 +40,7 @@ Object {
|
|||
"type": "mlSnapshot",
|
||||
},
|
||||
"details": "details",
|
||||
"index": undefined,
|
||||
"isCritical": true,
|
||||
"message": "model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded",
|
||||
"resolveDuringUpgrade": false,
|
||||
|
@ -46,6 +50,7 @@ Object {
|
|||
Object {
|
||||
"correctiveAction": undefined,
|
||||
"details": "This node thing is wrong",
|
||||
"index": undefined,
|
||||
"isCritical": true,
|
||||
"message": "A node-level issue",
|
||||
"resolveDuringUpgrade": true,
|
||||
|
@ -153,7 +158,17 @@ Object {
|
|||
"type": "index_settings",
|
||||
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
|
||||
},
|
||||
Object {
|
||||
"correctiveAction": undefined,
|
||||
"details": "This data stream has backing indices that were created before Elasticsearch 8.0.0",
|
||||
"index": "my-v7-data-stream",
|
||||
"isCritical": true,
|
||||
"message": "Old data stream with a compatibility version < 8.0",
|
||||
"resolveDuringUpgrade": false,
|
||||
"type": "data_streams",
|
||||
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-9.0.html",
|
||||
},
|
||||
],
|
||||
"totalCriticalDeprecations": 4,
|
||||
"totalCriticalDeprecations": 5,
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EnrichedDeprecationInfo } from '../../../common/types';
|
||||
|
||||
interface Action {
|
||||
action_type: 'remove_settings';
|
||||
objects: string[];
|
||||
}
|
||||
|
||||
interface Actions {
|
||||
actions: Action[];
|
||||
}
|
||||
|
||||
type EsMetadata = Actions & {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export const getCorrectiveAction = (
|
||||
message: string,
|
||||
metadata: EsMetadata,
|
||||
indexName?: string
|
||||
): EnrichedDeprecationInfo['correctiveAction'] => {
|
||||
const indexSettingDeprecation = metadata?.actions?.find(
|
||||
(action) => action.action_type === 'remove_settings' && indexName
|
||||
);
|
||||
const clusterSettingDeprecation = metadata?.actions?.find(
|
||||
(action) => action.action_type === 'remove_settings' && typeof indexName === 'undefined'
|
||||
);
|
||||
const requiresReindexAction = /Index created before/.test(message);
|
||||
const requiresIndexSettingsAction = Boolean(indexSettingDeprecation);
|
||||
const requiresClusterSettingsAction = Boolean(clusterSettingDeprecation);
|
||||
const requiresMlAction = /[Mm]odel snapshot/.test(message);
|
||||
|
||||
if (requiresReindexAction) {
|
||||
return {
|
||||
type: 'reindex',
|
||||
};
|
||||
}
|
||||
|
||||
if (requiresIndexSettingsAction) {
|
||||
return {
|
||||
type: 'indexSetting',
|
||||
deprecatedSettings: indexSettingDeprecation!.objects,
|
||||
};
|
||||
}
|
||||
|
||||
if (requiresClusterSettingsAction) {
|
||||
return {
|
||||
type: 'clusterSetting',
|
||||
deprecatedSettings: clusterSettingDeprecation!.objects,
|
||||
};
|
||||
}
|
||||
|
||||
if (requiresMlAction) {
|
||||
const { snapshot_id: snapshotId, job_id: jobId } = metadata!;
|
||||
|
||||
return {
|
||||
type: 'mlSnapshot',
|
||||
snapshotId,
|
||||
jobId,
|
||||
};
|
||||
}
|
||||
};
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { elasticsearchServiceMock, ScopedClusterClientMock } from '@kbn/core/server/mocks';
|
||||
import { getHealthIndicators } from './health_indicators';
|
||||
import * as healthIndicatorsMock from '../__fixtures__/health_indicators';
|
||||
|
||||
describe('getHealthIndicators', () => {
|
||||
let esClient: ScopedClusterClientMock;
|
||||
beforeEach(() => {
|
||||
esClient = elasticsearchServiceMock.createScopedClusterClient();
|
||||
});
|
||||
|
||||
it('returns empty array on green indicators', async () => {
|
||||
esClient.asCurrentUser.healthReport.mockResponse({
|
||||
cluster_name: 'mock',
|
||||
indicators: {
|
||||
disk: healthIndicatorsMock.diskIndicatorGreen,
|
||||
// @ts-ignore
|
||||
shards_capacity: healthIndicatorsMock.shardCapacityIndicatorGreen,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await getHealthIndicators(esClient);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns unknown indicators', async () => {
|
||||
esClient.asCurrentUser.healthReport.mockResponse({
|
||||
cluster_name: 'mock',
|
||||
indicators: {
|
||||
disk: healthIndicatorsMock.diskIndicatorUnknown,
|
||||
// @ts-ignore
|
||||
shards_capacity: healthIndicatorsMock.shardCapacityIndicatorGreen,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await getHealthIndicators(esClient);
|
||||
expect(result[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
details: 'No disk usage data.',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns unhealthy shards_capacity indicator', async () => {
|
||||
esClient.asCurrentUser.healthReport.mockResponse({
|
||||
cluster_name: 'mock',
|
||||
indicators: {
|
||||
disk: healthIndicatorsMock.diskIndicatorGreen,
|
||||
// @ts-ignore
|
||||
shards_capacity: healthIndicatorsMock.shardCapacityIndicatorRed,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await getHealthIndicators(esClient);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"correctiveAction": Object {
|
||||
"action": "Increase the value of [cluster.max_shards_per_node] cluster setting or remove data indices to clear up resources.",
|
||||
"cause": "Elasticsearch is about to reach the maximum number of shards it can host, based on your current settings.",
|
||||
"impacts": Array [
|
||||
Object {
|
||||
"description": "The cluster has too many used shards to be able to upgrade.",
|
||||
"id": "elasticsearch:health:shards_capacity:impact:upgrade_blocked",
|
||||
"impact_areas": "[Array]",
|
||||
"severity": 1,
|
||||
},
|
||||
Object {
|
||||
"description": "The cluster is running low on room to add new shards. Adding data to new indices is at risk",
|
||||
"id": "elasticsearch:health:shards_capacity:impact:creation_of_new_indices_blocked",
|
||||
"impact_areas": "[Array]",
|
||||
"severity": 1,
|
||||
},
|
||||
],
|
||||
"type": "healthIndicator",
|
||||
},
|
||||
"details": "Cluster is close to reaching the configured maximum number of shards for data nodes.",
|
||||
"isCritical": true,
|
||||
"message": "Elasticsearch is about to reach the maximum number of shards it can host, based on your current settings.",
|
||||
"resolveDuringUpgrade": false,
|
||||
"type": "health_indicator",
|
||||
"url": "https://ela.st/fix-shards-capacity",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('returns unhealthy disk indicator', async () => {
|
||||
esClient.asCurrentUser.healthReport.mockResponse({
|
||||
cluster_name: 'mock',
|
||||
indicators: {
|
||||
disk: healthIndicatorsMock.diskIndicatorRed,
|
||||
// @ts-ignore
|
||||
shards_capacity: healthIndicatorsMock.shardCapacityIndicatorGreen,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await getHealthIndicators(esClient);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"correctiveAction": Object {
|
||||
"cause": "The number of indices the system enforced a read-only index block (\`index.blocks.read_only_allow_delete\`) on because the cluster is running out of space.
|
||||
The number of nodes that are running low on disk and it is likely that they will run out of space. Their disk usage has tripped the <<cluster-routing-watermark-high, high watermark threshold>>.
|
||||
The number of nodes that have run out of disk. Their disk usage has tripped the <<cluster-routing-flood-stage, flood stagewatermark threshold>>.",
|
||||
"impacts": Object {
|
||||
"indices_with_readonly_block": 1,
|
||||
"nodes_over_flood_stage_watermark": 1,
|
||||
"nodes_over_high_watermark": 1,
|
||||
"nodes_with_enough_disk_space": 1,
|
||||
"nodes_with_unknown_disk_status": 1,
|
||||
},
|
||||
"type": "healthIndicator",
|
||||
},
|
||||
"details": "The cluster does not have enough available disk space.",
|
||||
"isCritical": true,
|
||||
"message": "The cluster does not have enough available disk space.",
|
||||
"resolveDuringUpgrade": false,
|
||||
"type": "health_indicator",
|
||||
"url": null,
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
import { EnrichedDeprecationInfo } from '../../../common/types';
|
||||
|
||||
export async function getHealthIndicators(
|
||||
dataClient: IScopedClusterClient
|
||||
): Promise<EnrichedDeprecationInfo[]> {
|
||||
const healthIndicators = await dataClient.asCurrentUser.healthReport();
|
||||
const isStatusNotGreen = (indicator?: estypes.HealthReportBaseIndicator): boolean => {
|
||||
return !!(indicator?.status && indicator?.status !== 'green');
|
||||
};
|
||||
|
||||
// Temporarily ignoring due to untyped ES indicators
|
||||
// types will be available during 8.9.0
|
||||
// @ts-ignore
|
||||
return [
|
||||
...[
|
||||
// @ts-ignore
|
||||
healthIndicators.indicators.shards_capacity as estypes.HealthReportBaseIndicator,
|
||||
]
|
||||
.filter(isStatusNotGreen)
|
||||
.flatMap(({ status, symptom, impacts, diagnosis }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
return (diagnosis || []).map(({ cause, action, help_url }) => ({
|
||||
type: 'health_indicator',
|
||||
details: symptom,
|
||||
message: cause,
|
||||
url: help_url,
|
||||
isCritical: status === 'red',
|
||||
resolveDuringUpgrade: false,
|
||||
correctiveAction: { type: 'healthIndicator', cause, action, impacts },
|
||||
}));
|
||||
}),
|
||||
...[healthIndicators.indicators.disk as estypes.HealthReportDiskIndicator]
|
||||
.filter(isStatusNotGreen)
|
||||
.flatMap(({ status, symptom, details }) => {
|
||||
return {
|
||||
type: 'health_indicator',
|
||||
isCritical: status === 'red',
|
||||
...getShardCapacityDeprecationInfo({ symptom, details }),
|
||||
};
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
export function getShardCapacityDeprecationInfo({
|
||||
symptom,
|
||||
details,
|
||||
}: {
|
||||
details: any;
|
||||
symptom: any;
|
||||
}) {
|
||||
// When we dont have a details field for our indicator, we can only report
|
||||
// the symptom to the user given that's the only information about the deprecation
|
||||
// we have.
|
||||
if (!details) {
|
||||
return {
|
||||
details: symptom,
|
||||
message: symptom,
|
||||
url: null,
|
||||
resolveDuringUpgrade: false,
|
||||
};
|
||||
}
|
||||
|
||||
const causes = [];
|
||||
if (details.indices_with_readonly_block > 0) {
|
||||
causes.push(
|
||||
i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecationsStatus.indicesWithReadonlyBlockCauseMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'The number of indices the system enforced a read-only index block (`index.blocks.read_only_allow_delete`) on because the cluster is running out of space.',
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (details.nodes_over_high_watermark > 0) {
|
||||
causes.push(
|
||||
i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecationsStatus.nodesOverHighWatermarkCauseMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'The number of nodes that are running low on disk and it is likely that they will run out of space. Their disk usage has tripped the <<cluster-routing-watermark-high, high watermark threshold>>.',
|
||||
ignoreTag: true,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (details.nodes_over_flood_stage_watermark > 0) {
|
||||
causes.push(
|
||||
i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecationsStatus.nodesOverFloodStageWatermarkCauseMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'The number of nodes that have run out of disk. Their disk usage has tripped the <<cluster-routing-flood-stage, flood stagewatermark threshold>>.',
|
||||
ignoreTag: true,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
details: symptom,
|
||||
message: symptom,
|
||||
url: null,
|
||||
resolveDuringUpgrade: false,
|
||||
correctiveAction: {
|
||||
type: 'healthIndicator',
|
||||
impacts: details,
|
||||
cause: causes.join('\n'),
|
||||
},
|
||||
};
|
||||
}
|
|
@ -6,142 +6,22 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { elasticsearchServiceMock, ScopedClusterClientMock } from '@kbn/core/server/mocks';
|
||||
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
||||
import { getESUpgradeStatus, getHealthIndicators } from './es_deprecations_status';
|
||||
import fakeDeprecations from './__fixtures__/fake_deprecations.json';
|
||||
import * as healthIndicatorsMock from './__fixtures__/health_indicators';
|
||||
|
||||
import type { FeatureSet } from '../../common/types';
|
||||
import fakeDeprecations from '../__fixtures__/fake_deprecations.json';
|
||||
import * as healthIndicatorsMock from '../__fixtures__/health_indicators';
|
||||
import * as esMigrationsMock from '../__fixtures__/es_deprecations';
|
||||
import type { FeatureSet } from '../../../common/types';
|
||||
import { getESUpgradeStatus } from '.';
|
||||
const fakeIndexNames = Object.keys(fakeDeprecations.index_settings);
|
||||
|
||||
describe('getHealthIndicators', () => {
|
||||
let esClient: ScopedClusterClientMock;
|
||||
beforeEach(() => {
|
||||
esClient = elasticsearchServiceMock.createScopedClusterClient();
|
||||
});
|
||||
|
||||
it('returns empty array on green indicators', async () => {
|
||||
esClient.asCurrentUser.healthReport.mockResponse({
|
||||
cluster_name: 'mock',
|
||||
indicators: {
|
||||
disk: healthIndicatorsMock.diskIndicatorGreen,
|
||||
// @ts-ignore
|
||||
shards_capacity: healthIndicatorsMock.shardCapacityIndicatorGreen,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await getHealthIndicators(esClient);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns unknown indicators', async () => {
|
||||
esClient.asCurrentUser.healthReport.mockResponse({
|
||||
cluster_name: 'mock',
|
||||
indicators: {
|
||||
disk: healthIndicatorsMock.diskIndicatorUnknown,
|
||||
// @ts-ignore
|
||||
shards_capacity: healthIndicatorsMock.shardCapacityIndicatorGreen,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await getHealthIndicators(esClient);
|
||||
expect(result[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
details: 'No disk usage data.',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns unhealthy shards_capacity indicator', async () => {
|
||||
esClient.asCurrentUser.healthReport.mockResponse({
|
||||
cluster_name: 'mock',
|
||||
indicators: {
|
||||
disk: healthIndicatorsMock.diskIndicatorGreen,
|
||||
// @ts-ignore
|
||||
shards_capacity: healthIndicatorsMock.shardCapacityIndicatorRed,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await getHealthIndicators(esClient);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"correctiveAction": Object {
|
||||
"action": "Increase the value of [cluster.max_shards_per_node] cluster setting or remove data indices to clear up resources.",
|
||||
"cause": "Elasticsearch is about to reach the maximum number of shards it can host, based on your current settings.",
|
||||
"impacts": Array [
|
||||
Object {
|
||||
"description": "The cluster has too many used shards to be able to upgrade.",
|
||||
"id": "elasticsearch:health:shards_capacity:impact:upgrade_blocked",
|
||||
"impact_areas": "[Array]",
|
||||
"severity": 1,
|
||||
},
|
||||
Object {
|
||||
"description": "The cluster is running low on room to add new shards. Adding data to new indices is at risk",
|
||||
"id": "elasticsearch:health:shards_capacity:impact:creation_of_new_indices_blocked",
|
||||
"impact_areas": "[Array]",
|
||||
"severity": 1,
|
||||
},
|
||||
],
|
||||
"type": "healthIndicator",
|
||||
},
|
||||
"details": "Cluster is close to reaching the configured maximum number of shards for data nodes.",
|
||||
"isCritical": true,
|
||||
"message": "Elasticsearch is about to reach the maximum number of shards it can host, based on your current settings.",
|
||||
"resolveDuringUpgrade": false,
|
||||
"type": "health_indicator",
|
||||
"url": "https://ela.st/fix-shards-capacity",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('returns unhealthy disk indicator', async () => {
|
||||
esClient.asCurrentUser.healthReport.mockResponse({
|
||||
cluster_name: 'mock',
|
||||
indicators: {
|
||||
disk: healthIndicatorsMock.diskIndicatorRed,
|
||||
// @ts-ignore
|
||||
shards_capacity: healthIndicatorsMock.shardCapacityIndicatorGreen,
|
||||
},
|
||||
});
|
||||
|
||||
const result = await getHealthIndicators(esClient);
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"correctiveAction": Object {
|
||||
"cause": "The number of indices the system enforced a read-only index block (\`index.blocks.read_only_allow_delete\`) on because the cluster is running out of space.
|
||||
The number of nodes that are running low on disk and it is likely that they will run out of space. Their disk usage has tripped the <<cluster-routing-watermark-high, high watermark threshold>>.
|
||||
The number of nodes that have run out of disk. Their disk usage has tripped the <<cluster-routing-flood-stage, flood stagewatermark threshold>>.",
|
||||
"impacts": Object {
|
||||
"indices_with_readonly_block": 1,
|
||||
"nodes_over_flood_stage_watermark": 1,
|
||||
"nodes_over_high_watermark": 1,
|
||||
"nodes_with_enough_disk_space": 1,
|
||||
"nodes_with_unknown_disk_status": 1,
|
||||
},
|
||||
"type": "healthIndicator",
|
||||
},
|
||||
"details": "The cluster does not have enough available disk space.",
|
||||
"isCritical": true,
|
||||
"message": "The cluster does not have enough available disk space.",
|
||||
"resolveDuringUpgrade": false,
|
||||
"type": "health_indicator",
|
||||
"url": null,
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getESUpgradeStatus', () => {
|
||||
const featureSet: FeatureSet = {
|
||||
reindexCorrectiveActions: true,
|
||||
migrateSystemIndices: true,
|
||||
mlSnapshots: true,
|
||||
migrateDataStreams: true,
|
||||
};
|
||||
|
||||
const resolvedIndices = {
|
||||
|
@ -200,6 +80,7 @@ describe('getESUpgradeStatus', () => {
|
|||
node_settings: [],
|
||||
ml_settings: [],
|
||||
index_settings: {},
|
||||
data_streams: {},
|
||||
});
|
||||
|
||||
await expect(getESUpgradeStatus(esClient, featureSet)).resolves.toHaveProperty(
|
||||
|
@ -215,6 +96,7 @@ describe('getESUpgradeStatus', () => {
|
|||
node_settings: [],
|
||||
ml_settings: [],
|
||||
index_settings: {},
|
||||
data_streams: {},
|
||||
});
|
||||
|
||||
await expect(getESUpgradeStatus(esClient, featureSet)).resolves.toHaveProperty(
|
||||
|
@ -240,6 +122,7 @@ describe('getESUpgradeStatus', () => {
|
|||
},
|
||||
],
|
||||
},
|
||||
data_streams: {},
|
||||
});
|
||||
|
||||
const upgradeStatus = await getESUpgradeStatus(esClient, featureSet);
|
||||
|
@ -249,38 +132,44 @@ describe('getESUpgradeStatus', () => {
|
|||
});
|
||||
|
||||
it('filters out ml_settings if featureSet.mlSnapshots is set to false', async () => {
|
||||
esClient.asCurrentUser.migration.deprecations.mockResponse({
|
||||
cluster_settings: [],
|
||||
node_settings: [],
|
||||
ml_settings: [
|
||||
{
|
||||
level: 'warning',
|
||||
message: 'Datafeed [deprecation-datafeed] uses deprecated query options',
|
||||
url: 'https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-7.0.html#breaking_70_search_changes',
|
||||
details:
|
||||
'[Deprecated field [use_dis_max] used, replaced by [Set [tie_breaker] to 1 instead]]',
|
||||
// @ts-ignore
|
||||
resolve_during_rolling_upgrade: false,
|
||||
},
|
||||
{
|
||||
level: 'critical',
|
||||
message:
|
||||
'model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded',
|
||||
url: '',
|
||||
details: 'details',
|
||||
// @ts-ignore
|
||||
_meta: { snapshot_id: '1', job_id: 'deprecation_check_job' },
|
||||
// @ts-ignore
|
||||
resolve_during_rolling_upgrade: false,
|
||||
},
|
||||
],
|
||||
index_settings: {},
|
||||
const mockResponse = {
|
||||
...esMigrationsMock.getMockEsDeprecations(),
|
||||
...esMigrationsMock.getMockMlSettingsDeprecations(),
|
||||
};
|
||||
// @ts-ignore missing property definitions in ES resolve_during_rolling_upgrade and _meta
|
||||
esClient.asCurrentUser.migration.deprecations.mockResponse(mockResponse);
|
||||
|
||||
const enabledUpgradeStatus = await getESUpgradeStatus(esClient, { ...featureSet });
|
||||
expect(enabledUpgradeStatus.deprecations).toHaveLength(2);
|
||||
expect(enabledUpgradeStatus.totalCriticalDeprecations).toBe(1);
|
||||
|
||||
const disabledUpgradeStatus = await getESUpgradeStatus(esClient, {
|
||||
...featureSet,
|
||||
mlSnapshots: false,
|
||||
});
|
||||
|
||||
const upgradeStatus = await getESUpgradeStatus(esClient, { ...featureSet, mlSnapshots: false });
|
||||
expect(disabledUpgradeStatus.deprecations).toHaveLength(0);
|
||||
expect(disabledUpgradeStatus.totalCriticalDeprecations).toBe(0);
|
||||
});
|
||||
|
||||
expect(upgradeStatus.deprecations).toHaveLength(0);
|
||||
expect(upgradeStatus.totalCriticalDeprecations).toBe(0);
|
||||
it('filters out data_streams if featureSet.migrateDataStreams is set to false', async () => {
|
||||
const mockResponse = {
|
||||
...esMigrationsMock.getMockEsDeprecations(),
|
||||
...esMigrationsMock.getMockDataStreamDeprecations(),
|
||||
};
|
||||
esClient.asCurrentUser.migration.deprecations.mockResponse(mockResponse);
|
||||
|
||||
const enabledUpgradeStatus = await getESUpgradeStatus(esClient, { ...featureSet });
|
||||
expect(enabledUpgradeStatus.deprecations).toHaveLength(1);
|
||||
expect(enabledUpgradeStatus.totalCriticalDeprecations).toBe(1);
|
||||
|
||||
const disabledUpgradeStatus = await getESUpgradeStatus(esClient, {
|
||||
...featureSet,
|
||||
migrateDataStreams: false,
|
||||
});
|
||||
|
||||
expect(disabledUpgradeStatus.deprecations).toHaveLength(0);
|
||||
expect(disabledUpgradeStatus.totalCriticalDeprecations).toBe(0);
|
||||
});
|
||||
|
||||
it('filters out reindex corrective actions if featureSet.reindexCorrectiveActions is set to false', async () => {
|
||||
|
@ -306,6 +195,7 @@ describe('getESUpgradeStatus', () => {
|
|||
],
|
||||
ml_settings: [],
|
||||
index_settings: {},
|
||||
data_streams: {},
|
||||
});
|
||||
|
||||
const upgradeStatus = await getESUpgradeStatus(esClient, {
|
||||
|
@ -332,6 +222,7 @@ describe('getESUpgradeStatus', () => {
|
|||
],
|
||||
ml_settings: [],
|
||||
index_settings: {},
|
||||
data_streams: {},
|
||||
});
|
||||
|
||||
esClient.asCurrentUser.healthReport.mockResponse({
|
||||
|
@ -380,6 +271,7 @@ describe('getESUpgradeStatus', () => {
|
|||
"type": "reindex",
|
||||
},
|
||||
"details": "This index was created using version: 6.8.13",
|
||||
"index": undefined,
|
||||
"isCritical": true,
|
||||
"message": "Index created before 7.0",
|
||||
"resolveDuringUpgrade": false,
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
import { EnrichedDeprecationInfo, ESUpgradeStatus, FeatureSet } from '../../../common/types';
|
||||
import { getEnrichedDeprecations } from './migrations';
|
||||
import { getHealthIndicators } from './health_indicators';
|
||||
|
||||
export async function getESUpgradeStatus(
|
||||
dataClient: IScopedClusterClient,
|
||||
featureSet: FeatureSet
|
||||
): Promise<ESUpgradeStatus> {
|
||||
const getCombinedDeprecations = async () => {
|
||||
const healthIndicators = await getHealthIndicators(dataClient);
|
||||
const enrichedDeprecations = await getEnrichedDeprecations(dataClient);
|
||||
|
||||
const toggledMigrationsDeprecations = enrichedDeprecations.filter(
|
||||
({ type, correctiveAction }) => {
|
||||
/**
|
||||
* This disables showing the ML deprecations in the UA if `featureSet.mlSnapshots`
|
||||
* is set to `false`.
|
||||
*
|
||||
* This config should be set to true only on the `x.last` versions, or when
|
||||
* the constant `MachineLearningField.MIN_CHECKED_SUPPORTED_SNAPSHOT_VERSION`
|
||||
* is incremented to something higher than 7.0.0 in the Elasticsearch code.
|
||||
*/
|
||||
if (type === 'ml_settings' || correctiveAction?.type === 'mlSnapshot') {
|
||||
return featureSet.mlSnapshots;
|
||||
}
|
||||
|
||||
/**
|
||||
* This disables showing the Data streams deprecations in the UA if
|
||||
* `featureSet.migrateDataStreams` is set to `false`.
|
||||
*/
|
||||
if (type === 'data_streams') {
|
||||
return !!featureSet.migrateDataStreams;
|
||||
}
|
||||
|
||||
/**
|
||||
* This disables showing the reindexing deprecations in the UA if
|
||||
* `featureSet.reindexCorrectiveActions` is set to `false`.
|
||||
*/
|
||||
if (correctiveAction?.type === 'reindex') {
|
||||
return !!featureSet.reindexCorrectiveActions;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
const enrichedHealthIndicators = healthIndicators.filter(({ status }) => {
|
||||
return status !== 'green';
|
||||
}) as EnrichedDeprecationInfo[];
|
||||
|
||||
return [...enrichedHealthIndicators, ...toggledMigrationsDeprecations];
|
||||
};
|
||||
|
||||
const combinedDeprecations = await getCombinedDeprecations();
|
||||
const criticalWarnings = combinedDeprecations.filter(({ isCritical }) => isCritical === true);
|
||||
|
||||
return {
|
||||
totalCriticalDeprecations: criticalWarnings.length,
|
||||
deprecations: combinedDeprecations,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type {
|
||||
MigrationDeprecationsResponse,
|
||||
MigrationDeprecationsDeprecation,
|
||||
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { IScopedClusterClient } from '@kbn/core-elasticsearch-server';
|
||||
import _ from 'lodash';
|
||||
import { EnrichedDeprecationInfo } from '../../../common/types';
|
||||
import {
|
||||
convertFeaturesToIndicesArray,
|
||||
getESSystemIndicesMigrationStatus,
|
||||
} from '../es_system_indices_migration';
|
||||
import { getCorrectiveAction } from './get_corrective_actions';
|
||||
import { esIndicesStateCheck } from '../es_indices_state_check';
|
||||
|
||||
/**
|
||||
* Remove once the data_streams type is added to the `MigrationDeprecationsResponse` type
|
||||
*/
|
||||
interface EsDeprecations extends MigrationDeprecationsResponse {
|
||||
data_streams: Record<string, MigrationDeprecationsDeprecation[]>;
|
||||
}
|
||||
|
||||
const createBaseMigrationDeprecation = (
|
||||
migrationDeprecation: MigrationDeprecationsDeprecation,
|
||||
{ deprecationType, indexName }: { deprecationType: keyof EsDeprecations; indexName?: string }
|
||||
) => {
|
||||
const {
|
||||
details,
|
||||
message,
|
||||
url,
|
||||
level,
|
||||
// @ts-expect-error @elastic/elasticsearch _meta not available yet in MigrationDeprecationInfoResponse
|
||||
_meta: metadata,
|
||||
// @ts-expect-error @elastic/elasticsearch resolve_during_rolling_upgrade not available yet in MigrationDeprecationInfoResponse
|
||||
resolve_during_rolling_upgrade: resolveDuringUpgrade,
|
||||
} = migrationDeprecation;
|
||||
|
||||
return {
|
||||
index: indexName,
|
||||
type: deprecationType,
|
||||
details,
|
||||
message,
|
||||
url,
|
||||
isCritical: level === 'critical',
|
||||
metadata,
|
||||
resolveDuringUpgrade,
|
||||
};
|
||||
};
|
||||
|
||||
const normalizeEsResponse = (migrationsResponse: EsDeprecations) => {
|
||||
const indexSettingsMigrations = Object.entries(migrationsResponse.index_settings).flatMap(
|
||||
([indexName, migrationDeprecations]) => {
|
||||
return migrationDeprecations.flatMap((migrationDeprecation) =>
|
||||
createBaseMigrationDeprecation(migrationDeprecation, {
|
||||
indexName,
|
||||
deprecationType: 'index_settings',
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const dataStreamsMigrations = Object.entries(migrationsResponse.data_streams).flatMap(
|
||||
([indexName, dataStreamDeprecations]) => {
|
||||
return dataStreamDeprecations.flatMap((depractionData) =>
|
||||
createBaseMigrationDeprecation(depractionData, {
|
||||
indexName,
|
||||
deprecationType: 'data_streams',
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const mlSettingsMigrations = migrationsResponse.ml_settings.map((depractionData) =>
|
||||
createBaseMigrationDeprecation(depractionData, { deprecationType: 'ml_settings' })
|
||||
);
|
||||
const nodeSettingsMigrations = migrationsResponse.node_settings.map((depractionData) =>
|
||||
createBaseMigrationDeprecation(depractionData, { deprecationType: 'node_settings' })
|
||||
);
|
||||
|
||||
const clusterSettingsMigrations = migrationsResponse.cluster_settings.map((depractionData) =>
|
||||
createBaseMigrationDeprecation(depractionData, { deprecationType: 'cluster_settings' })
|
||||
);
|
||||
|
||||
return [
|
||||
...clusterSettingsMigrations,
|
||||
...mlSettingsMigrations,
|
||||
...nodeSettingsMigrations,
|
||||
...indexSettingsMigrations,
|
||||
...dataStreamsMigrations,
|
||||
].flat();
|
||||
};
|
||||
|
||||
export const getEnrichedDeprecations = async (
|
||||
dataClient: IScopedClusterClient
|
||||
): Promise<EnrichedDeprecationInfo[]> => {
|
||||
const deprecations = (await dataClient.asCurrentUser.migration.deprecations()) as EsDeprecations;
|
||||
const systemIndices = await getESSystemIndicesMigrationStatus(dataClient.asCurrentUser);
|
||||
|
||||
const systemIndicesList = convertFeaturesToIndicesArray(systemIndices.features);
|
||||
|
||||
const indexSettingsIndexNames = Object.keys(deprecations.index_settings).map(
|
||||
(indexName) => indexName!
|
||||
);
|
||||
const indexSettingsIndexStates = indexSettingsIndexNames.length
|
||||
? await esIndicesStateCheck(dataClient.asCurrentUser, indexSettingsIndexNames)
|
||||
: {};
|
||||
|
||||
return normalizeEsResponse(deprecations)
|
||||
.filter((deprecation) => {
|
||||
switch (deprecation.type) {
|
||||
case 'index_settings': {
|
||||
if (!deprecation.index) {
|
||||
return false;
|
||||
}
|
||||
// filter out system indices
|
||||
return !systemIndicesList.includes(deprecation.index);
|
||||
}
|
||||
case 'cluster_settings':
|
||||
case 'ml_settings':
|
||||
case 'node_settings':
|
||||
case 'data_streams': {
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
// Throwing here to avoid allowing upgrades while we have unhandled deprecation types from ES
|
||||
// That might cause the stack to fail to start after upgrade.
|
||||
throw new Error(`Unknown ES deprecation type "${deprecation.type}"`);
|
||||
}
|
||||
}
|
||||
})
|
||||
.map((deprecation) => {
|
||||
const correctiveAction = getCorrectiveAction(
|
||||
deprecation.message,
|
||||
deprecation.metadata,
|
||||
deprecation.index!
|
||||
);
|
||||
|
||||
// If we have found deprecation information for index/indices
|
||||
// check whether the index is open or closed.
|
||||
if (deprecation.type === 'index_settings' && correctiveAction?.type === 'reindex') {
|
||||
correctiveAction.blockerForReindexing =
|
||||
indexSettingsIndexStates[deprecation.index!] === 'closed' ? 'index-closed' : undefined;
|
||||
}
|
||||
|
||||
const enrichedDeprecation = _.omit(deprecation, 'metadata');
|
||||
return {
|
||||
...enrichedDeprecation,
|
||||
correctiveAction,
|
||||
};
|
||||
});
|
||||
};
|
|
@ -42,7 +42,8 @@
|
|||
"@kbn/deeplinks-observability",
|
||||
"@kbn/core-logging-server-mocks",
|
||||
"@kbn/core-http-server-mocks",
|
||||
"@kbn/core-http-server-utils"
|
||||
"@kbn/core-http-server-utils",
|
||||
"@kbn/core-elasticsearch-server"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -13,5 +13,15 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
return {
|
||||
...baseIntegrationTestsConfig.getAll(),
|
||||
testFiles: [require.resolve('.')],
|
||||
kbnTestServer: {
|
||||
...baseIntegrationTestsConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...baseIntegrationTestsConfig.get('kbnTestServer.serverArgs'),
|
||||
'--xpack.upgrade_assistant.featureSet.mlSnapshots=true',
|
||||
'--xpack.upgrade_assistant.featureSet.migrateSystemIndices=true',
|
||||
'--xpack.upgrade_assistant.featureSet.migrateDataStreams=true',
|
||||
'--xpack.upgrade_assistant.featureSet.reindexCorrectiveActions=true',
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,8 +14,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
const es = getService('es');
|
||||
const supertest = getService('supertest');
|
||||
|
||||
// Failing: See https://github.com/elastic/kibana/issues/199719
|
||||
describe.skip('Upgrade Assistant', function () {
|
||||
describe('Upgrade Assistant', function () {
|
||||
describe('Reindex operation saved object', () => {
|
||||
const dotKibanaIndex = '.kibana';
|
||||
const fakeSavedObjectId = 'fakeSavedObjectId';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue