Skip to content

Commit e1d9aa6

Browse files
[7.x] [Upgrade Assistant] Better handling of closed indices (elastic#58890) (elastic#59650)
* [Upgrade Assistant] Better handling of closed indices (elastic#58890) * Exclude disallowed, private setting at index creation * Remove intl from tabs component * Added logic for checking the current index status * Added ES contract integration test Using _cluster/state is considered internal. This adds an integration test for checking the contract in CI. * Add the client side notification for closed indices * First version of end-to-end functionality working * Clean up unused, incorrect type information * Fix type issues and added a comment about the new reindex options * Fixed server side tests, added comments and logic updates Updated the handling of reindexOptions to make it more backwards compatible (treat it as if it could be undefined). Also update the logic for checking for open or closed indices. No optional chaining! It should break if the response does not exactly match. * Clean up unused code * Improved idempotency of test and check explicitly for "close". Rather check for the specific value we want, as this is what is also gauranteed by the tests. In this way, the information we send back to the client is also more accurate regarding the index status. If, in future, more index states are introduced this will need to be revisited if it affects the ability for an index to be re-indexed. * Update client-side tests * Fix types * Handle a case where the index name provided may be an alias * Fix types * merge-conflict: finish merge conflict resolution * Update x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/closed_warning_icon.tsx Co-Authored-By: Alison Goryachev <[email protected]> * merge-conflict: Remove duplicate import VSCode does not auto-save as expected :sigh: * ui: Revisit the UI Moved the warning icon to inside of the button and tooltip to on the button. Added a callout to the reindex flyout for when an index is closed. * logic: slight update to when the index closed callout is shown We only show the index closed callout in the flyout when the reindex operation is not considered "completed" * tests: fix jest tests * refactor: remove "openAndClose" from reindex endpoints "openAndClose" should just happen automatically. The user should not have to pass the flag in, that would be a weird API. We just need to warn the user about that reindexing a closed index will take more resources * test: update upgrade assistant integration test * fix: types * copy: use sentence case * refactor: use the in scope declaration of reindex op * test: Clean up tests Reindexing test was generating index name, could just get it from server response. Also removed openAndClose from all integration tests Co-authored-by: Elastic Machine <[email protected]> Co-authored-by: Alison Goryachev <[email protected]> # Conflicts: # x-pack/plugins/upgrade_assistant/common/types.ts # x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/cell.tsx # x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.tsx # x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.test.tsx # x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx # x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/button.tsx # x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/checklist_step.test.tsx # x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/checklist_step.tsx # x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/container.tsx # x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_migration_apis.test.ts.snap # x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts * Merge: Resolve remaining merge conflicts Also updated existing Jest snapshots. * test: Update Jest snapshots and reference the 6.0 index The integration test on master is reference 7.0 because it aims toward helping users migrate from 7.0 -> 8.0. The 7.x branch is running 6.0 data as it helps migrate from older indices. * Remove test data 🤦🏼‍♂️ * Clean up Jest snapshot After removing the test data, this snapshot also needed to be updated Co-authored-by: Elastic Machine <[email protected]>
1 parent 24c33e2 commit e1d9aa6

32 files changed

+594
-127
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import { getIndexStateFromClusterState } from './get_index_state_from_cluster_state';
7+
import { ClusterStateAPIResponse } from './types';
8+
9+
describe('getIndexStateFromClusterState', () => {
10+
const indexName = 'indexName';
11+
const clusterState: ClusterStateAPIResponse = {
12+
metadata: {
13+
indices: {},
14+
cluster_coordination: {} as any,
15+
cluster_uuid: 'test',
16+
templates: {} as any,
17+
},
18+
cluster_name: 'test',
19+
cluster_uuid: 'test',
20+
};
21+
22+
afterEach(() => {
23+
clusterState.metadata.indices = {};
24+
});
25+
26+
it('correctly extracts state from cluster state', () => {
27+
clusterState.metadata.indices[indexName] = { state: 'open' } as any;
28+
clusterState.metadata.indices.aTotallyDifferentIndex = { state: 'close' } as any;
29+
expect(getIndexStateFromClusterState(indexName, clusterState)).toBe('open');
30+
});
31+
32+
it('correctly extracts state from aliased index in cluster state', () => {
33+
clusterState.metadata.indices.aTotallyDifferentName = {
34+
state: 'close',
35+
aliases: [indexName, 'test'],
36+
} as any;
37+
clusterState.metadata.indices.aTotallyDifferentName1 = {
38+
state: 'open',
39+
aliases: ['another', 'test'],
40+
} as any;
41+
42+
expect(getIndexStateFromClusterState(indexName, clusterState)).toBe('close');
43+
});
44+
45+
it('throws if the index name cannot be found in the cluster state', () => {
46+
expect(() => getIndexStateFromClusterState(indexName, clusterState)).toThrow('not found');
47+
clusterState.metadata.indices.aTotallyDifferentName1 = {
48+
state: 'open',
49+
aliases: ['another', 'test'],
50+
} as any;
51+
expect(() => getIndexStateFromClusterState(indexName, clusterState)).toThrow('not found');
52+
});
53+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { ClusterStateAPIResponse } from './types';
8+
9+
const checkAllAliases = (
10+
indexName: string,
11+
clusterState: ClusterStateAPIResponse
12+
): 'open' | 'close' => {
13+
for (const index of Object.values(clusterState.metadata.indices)) {
14+
if (index.aliases?.some(alias => alias === indexName)) {
15+
return index.state;
16+
}
17+
}
18+
19+
throw new Error(`${indexName} not found in cluster state!`);
20+
};
21+
22+
export const getIndexStateFromClusterState = (
23+
indexName: string,
24+
clusterState: ClusterStateAPIResponse
25+
): 'open' | 'close' =>
26+
clusterState.metadata.indices[indexName]
27+
? clusterState.metadata.indices[indexName].state
28+
: checkAllAliases(indexName, clusterState);

x-pack/plugins/upgrade_assistant/common/types.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ export interface QueueSettings extends SavedObjectAttributes {
3434
}
3535

3636
export interface ReindexOptions extends SavedObjectAttributes {
37+
/**
38+
* Whether to treat the index as if it were closed. This instructs the
39+
* reindex strategy to first open the index, perform reindexing and
40+
* then close the index again.
41+
*/
42+
openAndClose?: boolean;
43+
3744
/**
3845
* Set this key to configure a reindex operation as part of a
3946
* batch to be run in series.
@@ -50,7 +57,6 @@ export interface ReindexOperation extends SavedObjectAttributes {
5057
reindexTaskId: string | null;
5158
reindexTaskPercComplete: number | null;
5259
errorMessage: string | null;
53-
5460
// This field is only used for the singleton IndexConsumerType documents.
5561
runningReindexCount: number | null;
5662

@@ -142,10 +148,57 @@ export interface EnrichedDeprecationInfo extends DeprecationInfo {
142148
node?: string;
143149
reindex?: boolean;
144150
needsDefaultFields?: boolean;
151+
/**
152+
* Indicate what blockers have been detected for calling reindex
153+
* against this index.
154+
*
155+
* @remark
156+
* In future this could be an array of blockers.
157+
*/
158+
blockerForReindexing?: 'index-closed'; // 'index-closed' can be handled automatically, but requires more resources, user should be warned
145159
}
146160

147161
export interface UpgradeAssistantStatus {
148162
readyForUpgrade: boolean;
149163
cluster: EnrichedDeprecationInfo[];
150164
indices: EnrichedDeprecationInfo[];
151165
}
166+
167+
export interface ClusterStateIndexAPIResponse {
168+
state: 'open' | 'close';
169+
settings: {
170+
index: {
171+
verified_before_close: string;
172+
search: {
173+
throttled: string;
174+
};
175+
number_of_shards: string;
176+
provided_name: string;
177+
frozen: string;
178+
creation_date: string;
179+
number_of_replicas: string;
180+
uuid: string;
181+
version: {
182+
created: string;
183+
};
184+
};
185+
};
186+
mappings: any;
187+
aliases: string[];
188+
}
189+
190+
export interface ClusterStateAPIResponse {
191+
cluster_name: string;
192+
cluster_uuid: string;
193+
metadata: {
194+
cluster_uuid: string;
195+
cluster_coordination: {
196+
term: number;
197+
last_committed_config: string[];
198+
last_accepted_config: string[];
199+
voting_config_exclusions: [];
200+
};
201+
templates: any;
202+
indices: { [indexName: string]: ClusterStateIndexAPIResponse };
203+
};
204+
}

x-pack/plugins/upgrade_assistant/public/application/app_context.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
* or more contributor license agreements. Licensed under the Elastic License;
44
* you may not use this file except in compliance with the Elastic License.
55
*/
6-
import { HttpSetup } from 'src/core/public';
6+
import { DocLinksStart, HttpSetup } from 'src/core/public';
77
import React, { createContext, useContext } from 'react';
88

99
export interface ContextValue {
1010
http: HttpSetup;
1111
isCloudEnabled: boolean;
12+
docLinks: DocLinksStart;
1213
}
1314

1415
export const AppContext = createContext<ContextValue>({} as any);

x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414
EuiTabbedContent,
1515
EuiTabbedContentTab,
1616
} from '@elastic/eui';
17-
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
17+
import { i18n } from '@kbn/i18n';
18+
import { FormattedMessage } from '@kbn/i18n/react';
1819
import { HttpSetup } from 'src/core/public';
1920

2021
import { UpgradeAssistantStatus } from '../../../common/types';
@@ -38,9 +39,11 @@ interface TabsState {
3839
clusterUpgradeState: ClusterUpgradeState;
3940
}
4041

41-
type Props = ReactIntl.InjectedIntlProps & { http: HttpSetup };
42+
interface Props {
43+
http: HttpSetup;
44+
}
4245

43-
export class UpgradeAssistantTabsUI extends React.Component<Props, TabsState> {
46+
export class UpgradeAssistantTabs extends React.Component<Props, TabsState> {
4447
constructor(props: Props) {
4548
super(props);
4649
this.state = {
@@ -172,7 +175,6 @@ export class UpgradeAssistantTabsUI extends React.Component<Props, TabsState> {
172175
};
173176

174177
private get tabs() {
175-
const { intl } = this.props;
176178
const { loadingError, loadingState, checkupData } = this.state;
177179
const commonProps: UpgradeAssistantTabProps = {
178180
loadingError,
@@ -186,24 +188,21 @@ export class UpgradeAssistantTabsUI extends React.Component<Props, TabsState> {
186188
return [
187189
{
188190
id: 'overview',
189-
name: intl.formatMessage({
190-
id: 'xpack.upgradeAssistant.overviewTab.overviewTabTitle',
191+
name: i18n.translate('xpack.upgradeAssistant.overviewTab.overviewTabTitle', {
191192
defaultMessage: 'Overview',
192193
}),
193194
content: <OverviewTab checkupData={checkupData} {...commonProps} />,
194195
},
195196
{
196197
id: 'cluster',
197-
name: intl.formatMessage({
198-
id: 'xpack.upgradeAssistant.checkupTab.clusterTabLabel',
198+
name: i18n.translate('xpack.upgradeAssistant.checkupTab.clusterTabLabel', {
199199
defaultMessage: 'Cluster',
200200
}),
201201
content: (
202202
<CheckupTab
203203
key="cluster"
204204
deprecations={checkupData ? checkupData.cluster : undefined}
205-
checkupLabel={intl.formatMessage({
206-
id: 'xpack.upgradeAssistant.tabs.checkupTab.clusterLabel',
205+
checkupLabel={i18n.translate('xpack.upgradeAssistant.tabs.checkupTab.clusterLabel', {
207206
defaultMessage: 'cluster',
208207
})}
209208
{...commonProps}
@@ -212,16 +211,14 @@ export class UpgradeAssistantTabsUI extends React.Component<Props, TabsState> {
212211
},
213212
{
214213
id: 'indices',
215-
name: intl.formatMessage({
216-
id: 'xpack.upgradeAssistant.checkupTab.indicesTabLabel',
214+
name: i18n.translate('xpack.upgradeAssistant.checkupTab.indicesTabLabel', {
217215
defaultMessage: 'Indices',
218216
}),
219217
content: (
220218
<CheckupTab
221219
key="indices"
222220
deprecations={checkupData ? checkupData.indices : undefined}
223-
checkupLabel={intl.formatMessage({
224-
id: 'xpack.upgradeAssistant.checkupTab.indexLabel',
221+
checkupLabel={i18n.translate('xpack.upgradeAssistant.checkupTab.indexLabel', {
225222
defaultMessage: 'index',
226223
})}
227224
showBackupWarning
@@ -249,5 +246,3 @@ export class UpgradeAssistantTabsUI extends React.Component<Props, TabsState> {
249246
this.setState({ telemetryState: TelemetryState.Complete });
250247
}
251248
}
252-
253-
export const UpgradeAssistantTabs = injectI18n(UpgradeAssistantTabsUI);

x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/cell.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
1919
import { FixDefaultFieldsButton } from './default_fields/button';
2020
import { ReindexButton } from './reindex';
2121
import { AppContext } from '../../../../app_context';
22+
import { EnrichedDeprecationInfo } from '../../../../../../common/types';
2223

2324
interface DeprecationCellProps {
2425
items?: Array<{ title?: string; body: string }>;
@@ -29,6 +30,7 @@ interface DeprecationCellProps {
2930
headline?: string;
3031
healthColor?: string;
3132
children?: ReactNode;
33+
reindexBlocker?: EnrichedDeprecationInfo['blockerForReindexing'];
3234
}
3335

3436
/**
@@ -43,6 +45,7 @@ export const DeprecationCell: FunctionComponent<DeprecationCellProps> = ({
4345
docUrl,
4446
items = [],
4547
children,
48+
reindexBlocker,
4649
}) => (
4750
<div className="upgDeprecationCell">
4851
<EuiFlexGroup responsive={false} wrap alignItems="baseline">
@@ -84,7 +87,14 @@ export const DeprecationCell: FunctionComponent<DeprecationCellProps> = ({
8487
{reindex && (
8588
<EuiFlexItem grow={false}>
8689
<AppContext.Consumer>
87-
{({ http }) => <ReindexButton indexName={indexName!} http={http} />}
90+
{({ http, docLinks }) => (
91+
<ReindexButton
92+
docLinks={docLinks}
93+
reindexBlocker={reindexBlocker}
94+
indexName={indexName!}
95+
http={http}
96+
/>
97+
)}
8898
</AppContext.Consumer>
8999
</EuiFlexItem>
90100
)}

x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/index_table.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import { injectI18n } from '@kbn/i18n/react';
1212
import { FixDefaultFieldsButton } from './default_fields/button';
1313
import { ReindexButton } from './reindex';
1414
import { AppContext } from '../../../../app_context';
15+
import { EnrichedDeprecationInfo } from '../../../../../../common/types';
1516

1617
const PAGE_SIZES = [10, 25, 50, 100, 250, 500, 1000];
1718

1819
export interface IndexDeprecationDetails {
1920
index: string;
2021
reindex: boolean;
2122
needsDefaultFields: boolean;
23+
blockerForReindexing?: EnrichedDeprecationInfo['blockerForReindexing'];
2224
details?: string;
2325
}
2426

@@ -70,9 +72,10 @@ export class IndexDeprecationTableUI extends React.Component<
7072
},
7173
];
7274

73-
if (this.actionsColumn) {
74-
// @ts-ignore
75-
columns.push(this.actionsColumn);
75+
const actionsColumn = this.generateActionsColumn();
76+
77+
if (actionsColumn) {
78+
columns.push(actionsColumn as any);
7679
}
7780

7881
const sorting = {
@@ -136,7 +139,7 @@ export class IndexDeprecationTableUI extends React.Component<
136139
return { totalItemCount, pageSizeOptions, hidePerPageOptions: false };
137140
}
138141

139-
private get actionsColumn() {
142+
private generateActionsColumn() {
140143
// NOTE: this naive implementation assumes all indices in the table are
141144
// should show the reindex button. This should work for known usecases.
142145
const { indices } = this.props;
@@ -153,7 +156,16 @@ export class IndexDeprecationTableUI extends React.Component<
153156
if (showReindexButton) {
154157
return (
155158
<AppContext.Consumer>
156-
{({ http }) => <ReindexButton indexName={indexDep.index!} http={http} />}
159+
{({ http, docLinks }) => {
160+
return (
161+
<ReindexButton
162+
docLinks={docLinks}
163+
reindexBlocker={indexDep.blockerForReindexing}
164+
indexName={indexDep.index!}
165+
http={http}
166+
/>
167+
);
168+
}}
157169
</AppContext.Consumer>
158170
);
159171
} else {

0 commit comments

Comments
 (0)