Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ILM] Add "wait for snapshot" policy field to Delete phase #68505

Merged
merged 4 commits into from
Jun 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export const POLICY_NAME = 'my_policy';

export const DELETE_PHASE_POLICY = {
version: 1,
modified_date: Date.now(),
policy: {
phases: {
hot: {
min_age: '0ms',
actions: {
rollover: {
max_size: '50gb',
},
},
},
delete: {
min_age: '0ms',
actions: {
wait_for_snapshot: {
policy: 'my_snapshot_policy',
},
delete: {
delete_searchable_snapshot: true,
},
},
},
},
},
name: POLICY_NAME,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { act } from 'react-dom/test-utils';

import { registerTestBed, TestBed, TestBedConfig } from '../../../../../test_utils';

import { POLICY_NAME } from './contants';
import { TestSubjects } from '../helpers';

import { EditPolicy } from '../../../public/application/sections/edit_policy';
import { indexLifecycleManagementStore } from '../../../public/application/store';

const testBedConfig: TestBedConfig = {
store: () => indexLifecycleManagementStore(),
memoryRouter: {
initialEntries: [`/policies/edit/${POLICY_NAME}`],
componentRoutePath: `/policies/edit/:policyName`,
},
};

const initTestBed = registerTestBed(EditPolicy, testBedConfig);

export interface EditPolicyTestBed extends TestBed<TestSubjects> {
actions: {
setWaitForSnapshotPolicy: (snapshotPolicyName: string) => void;
savePolicy: () => void;
};
}

export const setup = async (): Promise<EditPolicyTestBed> => {
const testBed = await initTestBed();

const setWaitForSnapshotPolicy = (snapshotPolicyName: string) => {
const { component, form } = testBed;
form.setInputValue('waitForSnapshotField', snapshotPolicyName, true);
component.update();
};

const savePolicy = async () => {
const { component, find } = testBed;
await act(async () => {
find('savePolicyButton').simulate('click');
});
component.update();
};

return {
...testBed,
actions: {
setWaitForSnapshotPolicy,
savePolicy,
},
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { act } from 'react-dom/test-utils';

import { setupEnvironment } from '../helpers/setup_environment';

import { EditPolicyTestBed, setup } from './edit_policy.helpers';
import { DELETE_PHASE_POLICY } from './contants';

import { API_BASE_PATH } from '../../../common/constants';

window.scrollTo = jest.fn();

describe('<EditPolicy />', () => {
let testBed: EditPolicyTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
afterAll(() => {
server.restore();
});

describe('delete phase', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setLoadPolicies([DELETE_PHASE_POLICY]);

await act(async () => {
testBed = await setup();
});

const { component } = testBed;
component.update();
});

test('wait for snapshot policy field should correctly display snapshot policy name', () => {
expect(testBed.find('waitForSnapshotField').props().value).toEqual(
DELETE_PHASE_POLICY.policy.phases.delete.actions.wait_for_snapshot.policy
);
});

test('wait for snapshot field should correctly update snapshot policy name', async () => {
const { actions } = testBed;

const newPolicyName = 'my_new_snapshot_policy';
actions.setWaitForSnapshotPolicy(newPolicyName);
await actions.savePolicy();

const expected = {
name: DELETE_PHASE_POLICY.name,
phases: {
...DELETE_PHASE_POLICY.policy.phases,
delete: {
...DELETE_PHASE_POLICY.policy.phases.delete,
actions: {
...DELETE_PHASE_POLICY.policy.phases.delete.actions,
wait_for_snapshot: {
policy: newPolicyName,
},
},
},
},
};

const latestRequest = server.requests[server.requests.length - 1];
expect(latestRequest.url).toBe(`${API_BASE_PATH}/policies`);
expect(latestRequest.method).toBe('POST');
expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual(expected);
});

test('wait for snapshot field should delete action if field is empty', async () => {
const { actions } = testBed;

actions.setWaitForSnapshotPolicy('');
await actions.savePolicy();

const expected = {
name: DELETE_PHASE_POLICY.name,
phases: {
...DELETE_PHASE_POLICY.policy.phases,
delete: {
...DELETE_PHASE_POLICY.policy.phases.delete,
actions: {
...DELETE_PHASE_POLICY.policy.phases.delete.actions,
},
},
},
};
delete expected.phases.delete.actions.wait_for_snapshot;

const latestRequest = server.requests[server.requests.length - 1];
expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual(expected);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { SinonFakeServer, fakeServer } from 'sinon';
import { API_BASE_PATH } from '../../../common/constants';

export const init = () => {
const server = fakeServer.create();
server.respondImmediately = true;
server.respondWith([200, {}, 'DefaultServerResponse']);

return {
server,
httpRequestsMockHelpers: registerHttpRequestMockHelpers(server),
};
};

const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
const setLoadPolicies = (response: any = []) => {
server.respondWith('GET', `${API_BASE_PATH}/policies`, [
200,
{ 'Content-Type': 'application/json' },
JSON.stringify(response),
]);
};

return {
setLoadPolicies,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export type TestSubjects = 'waitForSnapshotField' | 'savePolicyButton';
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import axios from 'axios';
import axiosXhrAdapter from 'axios/lib/adapters/xhr';

import { init as initHttp } from '../../../public/application/services/http';
import { init as initHttpRequests } from './http_requests';
import { init as initUiMetric } from '../../../public/application/services/ui_metric';
import { init as initNotification } from '../../../public/application/services/notification';

import { usageCollectionPluginMock } from '../../../../../../src/plugins/usage_collection/public/mocks';

import {
notificationServiceMock,
fatalErrorsServiceMock,
} from '../../../../../../src/core/public/mocks';

const mockHttpClient = axios.create({ adapter: axiosXhrAdapter });

export const setupEnvironment = () => {
initUiMetric(usageCollectionPluginMock.createSetupContract());
initNotification(
notificationServiceMock.createSetupContract().toasts,
fatalErrorsServiceMock.createSetupContract()
);

mockHttpClient.interceptors.response.use(({ data }) => data);
initHttp(mockHttpClient);
const { server, httpRequestsMockHelpers } = initHttpRequests();

return {
server,
httpRequestsMockHelpers,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export const PHASE_PRIMARY_SHARD_COUNT: string = 'selectedPrimaryShardCount';
export const PHASE_REPLICA_COUNT: string = 'selectedReplicaCount';
export const PHASE_INDEX_PRIORITY: string = 'phaseIndexPriority';

export const PHASE_WAIT_FOR_SNAPSHOT_POLICY = 'waitForSnapshotPolicy';

export const PHASE_ATTRIBUTES_THAT_ARE_NUMBERS_VALIDATE: string[] = [
PHASE_ROLLOVER_MINIMUM_AGE,
PHASE_FORCE_MERGE_SEGMENTS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@
import React, { PureComponent, Fragment } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiDescribedFormGroup, EuiSwitch } from '@elastic/eui';
import {
EuiDescribedFormGroup,
EuiSwitch,
EuiFieldText,
EuiTextColor,
EuiFormRow,
} from '@elastic/eui';

import { PHASE_DELETE, PHASE_ENABLED } from '../../../../constants';
import { ActiveBadge, PhaseErrorMessage } from '../../../components';
import { PHASE_DELETE, PHASE_ENABLED, PHASE_WAIT_FOR_SNAPSHOT_POLICY } from '../../../../constants';
import { ActiveBadge, LearnMoreLink, OptionalLabel, PhaseErrorMessage } from '../../../components';
import { MinAgeInput } from '../min_age_input';

export class DeletePhase extends PureComponent {
Expand Down Expand Up @@ -85,6 +91,48 @@ export class DeletePhase extends PureComponent {
<div />
)}
</EuiDescribedFormGroup>
{phaseData[PHASE_ENABLED] ? (
<EuiDescribedFormGroup
title={
<h3>
<FormattedMessage
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.waitForSnapshotTitle"
defaultMessage="Wait for snapshot policy"
/>
</h3>
}
description={
<EuiTextColor color="subdued">
<FormattedMessage
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.waitForSnapshotDescription"
defaultMessage="Specify a snapshot policy to be executed before the deletion of the index. This ensures that a snapshot of the deleted index is available."
/>{' '}
<LearnMoreLink docPath="ilm-wait-for-snapshot.html" />
</EuiTextColor>
}
titleSize="xs"
fullWidth
>
<EuiFormRow
id="deletePhaseWaitForSnapshot"
label={
<Fragment>
<FormattedMessage
id="xpack.indexLifecycleMgmt.editPolicy.deletePhase.waitForSnapshotLabel"
defaultMessage="Snapshot policy name"
/>
<OptionalLabel />
</Fragment>
}
>
<EuiFieldText
data-test-subj="waitForSnapshotField"
value={phaseData[PHASE_WAIT_FOR_SNAPSHOT_POLICY]}
onChange={(e) => setPhaseData(PHASE_WAIT_FOR_SNAPSHOT_POLICY, e.target.value)}
/>
</EuiFormRow>
</EuiDescribedFormGroup>
) : null}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
PHASE_ROLLOVER_MINIMUM_AGE,
PHASE_ROLLOVER_MINIMUM_AGE_UNITS,
PHASE_ROLLOVER_ALIAS,
PHASE_WAIT_FOR_SNAPSHOT_POLICY,
} from '../../constants';

export const defaultDeletePhase = {
Expand All @@ -17,5 +18,6 @@ export const defaultDeletePhase = {
[PHASE_ROLLOVER_ALIAS]: '',
[PHASE_ROLLOVER_MINIMUM_AGE]: 0,
[PHASE_ROLLOVER_MINIMUM_AGE_UNITS]: 'd',
[PHASE_WAIT_FOR_SNAPSHOT_POLICY]: '',
};
export const defaultEmptyDeletePhase = defaultDeletePhase;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

export declare const defaultDeletePhase: any;
export declare const defaultColdPhase: any;
export declare const defaultWarmPhase: any;
export declare const defaultHotPhase: any;
Loading