Skip to content

Commit

Permalink
Feature: force merge (#608) (#634)
Browse files Browse the repository at this point in the history
* temp: save

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: update

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: add validator

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: add unit test

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: add unit test

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: add cypress test

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: try to get cypress test start

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: update

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: force merge optimize

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: modify toast message

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: update

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: update

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: update

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: update

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: update validation

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: add debounce

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: update validation

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: update unit test

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: make unit test run

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: modify id

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: update

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: remove useless code

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: update message

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: update

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: add readonly detect

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: protect

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: use the specific reason when force merge failed

Signed-off-by: suzhou <suzhou@amazon.com>

* feat: update

Signed-off-by: suzhou <suzhou@amazon.com>

---------

Signed-off-by: suzhou <suzhou@amazon.com>
(cherry picked from commit a7a2ad9)
  • Loading branch information
SuZhou-Joe authored Feb 22, 2023
1 parent 24bd906 commit a901f75
Show file tree
Hide file tree
Showing 25 changed files with 1,999 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cypress-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
with:
path: index-management
repository: opensearch-project/index-management
ref: 'main'
ref: '2.5'
- name: Run opensearch with plugin
run: |
cd index-management
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit-tests-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
branches:
- "*"
env:
OPENSEARCH_DASHBOARDS_VERSION: '2.x'
OPENSEARCH_DASHBOARDS_VERSION: '2.5'
jobs:
tests:
name: Run unit tests
Expand Down
78 changes: 78 additions & 0 deletions cypress/integration/force_merge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import { PLUGIN_NAME } from "../support/constants";

const rolloverValidAlias = "rollover-valid-alias";
const rolloverAliasNeedTargetIndex = "rollover-alias-need-target-index";
const rolloverDataStream = "data-stream-rollover";
const validIndex = "index-000001";
const invalidIndex = "index-test-rollover";

describe("force_merge", () => {
before(() => {
// Set welcome screen tracking to false
localStorage.setItem("home:welcome:show", "false");
cy.deleteTemplate("index-common-template");
cy.deleteAllIndices();
cy.request({
url: `${Cypress.env("opensearch")}/_data_stream/*`,
method: "DELETE",
failOnStatusCode: false,
});
cy.createIndex(validIndex);
cy.createIndex(invalidIndex);
cy.addAlias(rolloverValidAlias, validIndex);
cy.addAlias(rolloverAliasNeedTargetIndex, invalidIndex);
cy.createIndexTemplate("index-common-template", {
index_patterns: ["data-stream-*"],
data_stream: {},
template: {
aliases: {
alias_for_common_1: {},
alias_for_common_2: {},
},
settings: {
number_of_shards: 2,
number_of_replicas: 1,
},
},
});
cy.request({
url: `${Cypress.env("opensearch")}/_data_stream/${rolloverDataStream}`,
method: "PUT",
failOnStatusCode: false,
});
});

describe("force merge", () => {
it("force merge data stream / index / alias successfully", () => {
// Visit ISM OSD
cy.visit(`${Cypress.env("opensearch_dashboards")}/app/${PLUGIN_NAME}#/force-merge`);
cy.contains("Configure source index", { timeout: 60000 });

// click create
cy.get('[data-test-subj="forceMergeConfirmButton"]').click({ force: true });

cy.contains("Index or data stream is required.");
cy.get('[data-test-subj="sourceSelector"] [data-test-subj="comboBoxSearchInput"]').type(
`${rolloverValidAlias}{downArrow}{enter}${rolloverAliasNeedTargetIndex}{downArrow}{enter}${rolloverDataStream}{downArrow}{enter}${validIndex}{downArrow}{enter}${invalidIndex}{downArrow}{enter}`
);

cy.get('[data-test-subj="forceMergeConfirmButton"]').click({ force: true });

cy.contains(/Some shards could not be force merged/);
});
});

after(() => {
cy.deleteTemplate("index-common-template");
cy.deleteAllIndices();
cy.request({
url: `${Cypress.env("opensearch")}/_data_stream/*`,
method: "DELETE",
failOnStatusCode: false,
});
});
});
22 changes: 19 additions & 3 deletions public/components/FormGenerator/built_in_components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import React, { forwardRef } from "react";
import { EuiFieldNumber, EuiFieldText, EuiSwitch, EuiSelect, EuiText } from "@elastic/eui";
import React, { forwardRef, useRef } from "react";
import { EuiFieldNumber, EuiFieldText, EuiSwitch, EuiSelect, EuiText, EuiCheckbox } from "@elastic/eui";
import EuiToolTipWrapper, { IEuiToolTipWrapperProps } from "../../EuiToolTipWrapper";
import EuiComboBox from "../../ComboBoxWithoutWarning";

export type ComponentMapEnum = "Input" | "Number" | "Switch" | "Select" | "Text" | "ComboBoxSingle";
export type ComponentMapEnum = "Input" | "Number" | "Switch" | "Select" | "Text" | "ComboBoxSingle" | "CheckBox";

export interface IFieldComponentProps extends IEuiToolTipWrapperProps {
onChange: (val: IFieldComponentProps["value"]) => void;
value?: any;
[key: string]: any;
}

let globalId = 0;

const componentMap: Record<ComponentMapEnum, React.ComponentType<IFieldComponentProps>> = {
Input: EuiToolTipWrapper(
forwardRef(({ onChange, value, ...others }, ref: React.Ref<HTMLInputElement>) => (
Expand Down Expand Up @@ -39,6 +41,20 @@ const componentMap: Record<ComponentMapEnum, React.ComponentType<IFieldComponent
<EuiSelect inputRef={ref} onChange={(e) => onChange(e.target.value)} value={value || ""} {...others} />
)) as React.ComponentType<IFieldComponentProps>
),
CheckBox: EuiToolTipWrapper(
forwardRef(({ onChange, value, ...others }, ref: React.Ref<any>) => {
const idRef = useRef(globalId++);
return (
<EuiCheckbox
ref={ref}
id={`builtInCheckBoxId-${idRef.current}`}
checked={value === undefined ? false : value}
onChange={(e) => onChange(e.target.checked)}
{...others}
/>
);
}) as React.ComponentType<IFieldComponentProps>
),
ComboBoxSingle: EuiToolTipWrapper(
forwardRef(({ onChange, value, options, ...others }, ref: React.Ref<any>) => {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { render, waitFor } from "@testing-library/react";
import React from "react";
import ForceMergeAdvancedOptions, { ForceMergeOptionsProps } from "./ForceMergeAdvancedOptions";
import useField from "../../../../lib/field";

const WrappedComponent = (props: Partial<ForceMergeOptionsProps>) => {
const field = useField();
return <ForceMergeAdvancedOptions {...props} field={field} />;
};

describe("<ForceMergeAdvancedOptions /> spec", () => {
it("renders the component", async () => {
const component = render(<WrappedComponent />);
// wait for one tick
await waitFor(() => {});
expect(component).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import { EuiSpacer } from "@elastic/eui";
import React from "react";
import CustomFormRow from "../../../../components/CustomFormRow";
import { AllBuiltInComponents } from "../../../../components/FormGenerator";
import { FieldInstance } from "../../../../lib/field";
import SwitchNumber from "../SwitchNumber";

export interface ForceMergeOptionsProps {
field: FieldInstance;
}

const ForceMergeAdvancedOptions = (props: ForceMergeOptionsProps) => {
const { field } = props;

return (
<div style={{ padding: "10px 0px" }}>
<CustomFormRow
isInvalid={!!field.getError("max_num_segments")}
error={field.getError("max_num_segments")}
label="Segment indexes"
helpText="Define how many segments to merge to."
>
<SwitchNumber
{...field.registerField({
name: "max_num_segments",
rules: [
{
validator(rule, value) {
const formatValue = new Number(value);
if (Number.isNaN(formatValue.valueOf())) {
return Promise.resolve("");
} else if (formatValue.valueOf() % 1 !== 0 || formatValue.valueOf() < 1) {
return Promise.reject("Must be an integer great than or equal to 1.");
}

return Promise.resolve("");
},
},
],
})}
/>
</CustomFormRow>
<EuiSpacer />
<CustomFormRow label="Flush indexes" helpText="Opensearch will perform a flush on the indexes after the force merge.">
<AllBuiltInComponents.CheckBox
{...field.registerField({
name: "flush",
})}
label="Flush indexes"
/>
</CustomFormRow>
<EuiSpacer />
<CustomFormRow
label="Expunge deleted documents"
fullWidth
helpText="Expunge all segments containing more than 10% of deleted documents. The percentage is configurable with the setting index.merge.policy.expunge_deletes_allowed."
>
<AllBuiltInComponents.CheckBox
{...field.registerField({
name: "only_expunge_deletes",
})}
label="Only expunge delete"
/>
</CustomFormRow>
<EuiSpacer />
</div>
);
};

export default ForceMergeAdvancedOptions;
Loading

0 comments on commit a901f75

Please sign in to comment.