/*
 * 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 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */

package org.elasticsearch.client.documentation;

import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.LatchedActionListener;
import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest;
import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest;
import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.core.AcknowledgedResponse;
import org.elasticsearch.client.indexlifecycle.DeleteAction;
import org.elasticsearch.client.indexlifecycle.DeleteLifecyclePolicyRequest;
import org.elasticsearch.client.indexlifecycle.ExplainLifecycleRequest;
import org.elasticsearch.client.indexlifecycle.ExplainLifecycleResponse;
import org.elasticsearch.client.indexlifecycle.GetLifecyclePolicyRequest;
import org.elasticsearch.client.indexlifecycle.GetLifecyclePolicyResponse;
import org.elasticsearch.client.indexlifecycle.IndexLifecycleExplainResponse;
import org.elasticsearch.client.indexlifecycle.LifecycleAction;
import org.elasticsearch.client.indexlifecycle.LifecycleManagementStatusRequest;
import org.elasticsearch.client.indexlifecycle.LifecycleManagementStatusResponse;
import org.elasticsearch.client.indexlifecycle.LifecyclePolicy;
import org.elasticsearch.client.indexlifecycle.LifecyclePolicyMetadata;
import org.elasticsearch.client.indexlifecycle.OperationMode;
import org.elasticsearch.client.indexlifecycle.Phase;
import org.elasticsearch.client.indexlifecycle.PutLifecyclePolicyRequest;
import org.elasticsearch.client.indexlifecycle.RemoveIndexLifecyclePolicyRequest;
import org.elasticsearch.client.indexlifecycle.RemoveIndexLifecyclePolicyResponse;
import org.elasticsearch.client.indexlifecycle.RetryLifecyclePolicyRequest;
import org.elasticsearch.client.indexlifecycle.RolloverAction;
import org.elasticsearch.client.indexlifecycle.ShrinkAction;
import org.elasticsearch.client.indexlifecycle.StartILMRequest;
import org.elasticsearch.client.indexlifecycle.StopILMRequest;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.slm.DeleteSnapshotLifecyclePolicyRequest;
import org.elasticsearch.client.slm.ExecuteSnapshotLifecyclePolicyRequest;
import org.elasticsearch.client.slm.ExecuteSnapshotLifecyclePolicyResponse;
import org.elasticsearch.client.slm.ExecuteSnapshotLifecycleRetentionRequest;
import org.elasticsearch.client.slm.GetSnapshotLifecyclePolicyRequest;
import org.elasticsearch.client.slm.GetSnapshotLifecyclePolicyResponse;
import org.elasticsearch.client.slm.GetSnapshotLifecycleStatsRequest;
import org.elasticsearch.client.slm.GetSnapshotLifecycleStatsResponse;
import org.elasticsearch.client.slm.PutSnapshotLifecyclePolicyRequest;
import org.elasticsearch.client.slm.SnapshotInvocationRecord;
import org.elasticsearch.client.slm.SnapshotLifecycleManagementStatusRequest;
import org.elasticsearch.client.slm.SnapshotLifecyclePolicy;
import org.elasticsearch.client.slm.SnapshotLifecyclePolicyMetadata;
import org.elasticsearch.client.slm.SnapshotLifecycleStats;
import org.elasticsearch.client.slm.SnapshotRetentionConfiguration;
import org.elasticsearch.client.slm.StartSLMRequest;
import org.elasticsearch.client.slm.StopSLMRequest;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeUnit;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.repositories.fs.FsRepository;
import org.elasticsearch.snapshots.SnapshotInfo;
import org.elasticsearch.snapshots.SnapshotState;
import org.hamcrest.Matchers;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;

public class ILMDocumentationIT extends ESRestHighLevelClientTestCase {

    public void testPutLifecyclePolicy() throws Exception {
        RestHighLevelClient client = highLevelClient();

        // tag::ilm-put-lifecycle-policy-request
        Map<String, Phase> phases = new HashMap<>();
        Map<String, LifecycleAction> hotActions = new HashMap<>();
        hotActions.put(RolloverAction.NAME, new RolloverAction(
                new ByteSizeValue(50, ByteSizeUnit.GB), null, null, null));
        phases.put("hot", new Phase("hot", TimeValue.ZERO, hotActions)); // <1>

        Map<String, LifecycleAction> deleteActions =
                Collections.singletonMap(DeleteAction.NAME, new DeleteAction());
        phases.put("delete", new Phase("delete",
                new TimeValue(90, TimeUnit.DAYS), deleteActions)); // <2>

        LifecyclePolicy policy = new LifecyclePolicy("my_policy",
                phases); // <3>
        PutLifecyclePolicyRequest request =
                new PutLifecyclePolicyRequest(policy);
        // end::ilm-put-lifecycle-policy-request

        // tag::ilm-put-lifecycle-policy-execute
        AcknowledgedResponse response = client.indexLifecycle().
                putLifecyclePolicy(request, RequestOptions.DEFAULT);
        // end::ilm-put-lifecycle-policy-execute

        // tag::ilm-put-lifecycle-policy-response
        boolean acknowledged = response.isAcknowledged(); // <1>
        // end::ilm-put-lifecycle-policy-response

        assertTrue(acknowledged);

        // Delete the policy so it can be added again
        {
            DeleteLifecyclePolicyRequest deleteRequest =
                    new DeleteLifecyclePolicyRequest("my_policy");
            AcknowledgedResponse deleteResponse = client.indexLifecycle()
                    .deleteLifecyclePolicy(deleteRequest,
                            RequestOptions.DEFAULT);
            assertTrue(deleteResponse.isAcknowledged());
        }

        // tag::ilm-put-lifecycle-policy-execute-listener
        ActionListener<AcknowledgedResponse> listener =
                new ActionListener<AcknowledgedResponse>() {
            @Override
            public void onResponse(AcknowledgedResponse response) {
                boolean acknowledged = response.isAcknowledged(); // <1>
            }

            @Override
            public void onFailure(Exception e) {
                // <2>
            }
        };
        // end::ilm-put-lifecycle-policy-execute-listener

        // Replace the empty listener by a blocking listener in test
        final CountDownLatch latch = new CountDownLatch(1);
        listener = new LatchedActionListener<>(listener, latch);

        // tag::ilm-put-lifecycle-policy-execute-async
        client.indexLifecycle().putLifecyclePolicyAsync(request,
                RequestOptions.DEFAULT, listener); // <1>
        // end::ilm-put-lifecycle-policy-execute-async

        assertTrue(latch.await(30L, TimeUnit.SECONDS));

    }

    public void testDeletePolicy() throws IOException, InterruptedException {
        RestHighLevelClient client = highLevelClient();

        // Set up a policy so we have something to delete
        PutLifecyclePolicyRequest putRequest;
        {
            Map<String, Phase> phases = new HashMap<>();
            Map<String, LifecycleAction> hotActions = new HashMap<>();
            hotActions.put(RolloverAction.NAME, new RolloverAction(
                new ByteSizeValue(50, ByteSizeUnit.GB), null, null, null));
            phases.put("hot", new Phase("hot", TimeValue.ZERO, hotActions));
            Map<String, LifecycleAction> deleteActions =
                Collections.singletonMap(DeleteAction.NAME,
                    new DeleteAction());
            phases.put("delete",
                new Phase("delete",
                    new TimeValue(90, TimeUnit.DAYS), deleteActions));
            LifecyclePolicy myPolicy = new LifecyclePolicy("my_policy", phases);
            putRequest = new PutLifecyclePolicyRequest(myPolicy);
            AcknowledgedResponse putResponse = client.indexLifecycle().
                putLifecyclePolicy(putRequest, RequestOptions.DEFAULT);
            assertTrue(putResponse.isAcknowledged());
        }

        // tag::ilm-delete-lifecycle-policy-request
        DeleteLifecyclePolicyRequest request =
            new DeleteLifecyclePolicyRequest("my_policy"); // <1>
        // end::ilm-delete-lifecycle-policy-request

        // tag::ilm-delete-lifecycle-policy-execute
        AcknowledgedResponse response = client.indexLifecycle()
            .deleteLifecyclePolicy(request, RequestOptions.DEFAULT);
        // end::ilm-delete-lifecycle-policy-execute

        // tag::ilm-delete-lifecycle-policy-response
        boolean acknowledged = response.isAcknowledged(); // <1>
        // end::ilm-delete-lifecycle-policy-response

        assertTrue(acknowledged);

        // Put the policy again so we can delete it again
        {
            AcknowledgedResponse putResponse = client.indexLifecycle().
                putLifecyclePolicy(putRequest, RequestOptions.DEFAULT);
            assertTrue(putResponse.isAcknowledged());
        }

        // tag::ilm-delete-lifecycle-policy-execute-listener
        ActionListener<AcknowledgedResponse> listener =
            new ActionListener<AcknowledgedResponse>() {
                @Override
                public void onResponse(AcknowledgedResponse response) {
                    boolean acknowledged = response.isAcknowledged(); // <1>
                }

                @Override
                public void onFailure(Exception e) {
                    // <2>
                }
            };
        // end::ilm-delete-lifecycle-policy-execute-listener

        // Replace the empty listener by a blocking listener in test
        final CountDownLatch latch = new CountDownLatch(1);
        listener = new LatchedActionListener<>(listener, latch);

        // tag::ilm-delete-lifecycle-policy-execute-async
        client.indexLifecycle().deleteLifecyclePolicyAsync(request,
            RequestOptions.DEFAULT, listener); // <1>
        // end::ilm-delete-lifecycle-policy-execute-async

        assertTrue(latch.await(30L, TimeUnit.SECONDS));
    }

    public void testGetLifecyclePolicy() throws IOException, InterruptedException {
        RestHighLevelClient client = highLevelClient();

        LifecyclePolicy myPolicyAsPut;
        LifecyclePolicy otherPolicyAsPut;
        // Set up some policies so we have something to get
        {
            Map<String, Phase> phases = new HashMap<>();
            Map<String, LifecycleAction> hotActions = new HashMap<>();
            hotActions.put(RolloverAction.NAME, new RolloverAction(
                new ByteSizeValue(50, ByteSizeUnit.GB), null, null, null));
            phases.put("hot", new Phase("hot", TimeValue.ZERO, hotActions));

            Map<String, LifecycleAction> deleteActions =
                Collections.singletonMap(DeleteAction.NAME,
                    new DeleteAction());
            phases.put("delete",
                new Phase("delete",
                    new TimeValue(90, TimeUnit.DAYS), deleteActions));

            myPolicyAsPut = new LifecyclePolicy("my_policy", phases);
            PutLifecyclePolicyRequest putRequest = new PutLifecyclePolicyRequest(myPolicyAsPut);

            Map<String, Phase> otherPolicyPhases = new HashMap<>(phases);
            Map<String, LifecycleAction> warmActions = Collections.singletonMap(ShrinkAction.NAME, new ShrinkAction(1, null));
            otherPolicyPhases.put("warm", new Phase("warm", new TimeValue(30, TimeUnit.DAYS), warmActions));
            otherPolicyAsPut = new LifecyclePolicy("other_policy", otherPolicyPhases);

            PutLifecyclePolicyRequest putRequest2 = new PutLifecyclePolicyRequest(otherPolicyAsPut);

            AcknowledgedResponse putResponse = client.indexLifecycle().
                putLifecyclePolicy(putRequest, RequestOptions.DEFAULT);
            assertTrue(putResponse.isAcknowledged());
            AcknowledgedResponse putResponse2 = client.indexLifecycle().
                putLifecyclePolicy(putRequest2, RequestOptions.DEFAULT);
            assertTrue(putResponse2.isAcknowledged());
        }

        // tag::ilm-get-lifecycle-policy-request
        GetLifecyclePolicyRequest allRequest =
            new GetLifecyclePolicyRequest(); // <1>
        GetLifecyclePolicyRequest request =
            new GetLifecyclePolicyRequest("my_policy", "other_policy"); // <2>
        // end::ilm-get-lifecycle-policy-request

        // tag::ilm-get-lifecycle-policy-execute
        GetLifecyclePolicyResponse response = client.indexLifecycle()
            .getLifecyclePolicy(request, RequestOptions.DEFAULT);
        // end::ilm-get-lifecycle-policy-execute

        // tag::ilm-get-lifecycle-policy-response
        ImmutableOpenMap<String, LifecyclePolicyMetadata> policies =
            response.getPolicies();
        LifecyclePolicyMetadata myPolicyMetadata =
            policies.get("my_policy"); // <1>
        String myPolicyName = myPolicyMetadata.getName();
        long version = myPolicyMetadata.getVersion();
        String lastModified = myPolicyMetadata.getModifiedDateString();
        long lastModifiedDate = myPolicyMetadata.getModifiedDate();
        LifecyclePolicy myPolicy = myPolicyMetadata.getPolicy(); // <2>
        // end::ilm-get-lifecycle-policy-response

        assertEquals(myPolicyAsPut, myPolicy);
        assertEquals("my_policy", myPolicyName);
        assertNotNull(lastModified);
        assertNotEquals(0, lastModifiedDate);

        LifecyclePolicyMetadata otherPolicyMetadata = policies.get("other_policy");
        assertEquals(otherPolicyAsPut, otherPolicyMetadata.getPolicy());
        assertEquals("other_policy", otherPolicyMetadata.getName());
        assertNotNull(otherPolicyMetadata.getModifiedDateString());
        assertNotEquals(0, otherPolicyMetadata.getModifiedDate());

        // tag::ilm-get-lifecycle-policy-execute-listener
        ActionListener<GetLifecyclePolicyResponse> listener =
            new ActionListener<GetLifecyclePolicyResponse>() {
                @Override
                public void onResponse(GetLifecyclePolicyResponse response)
                {
                    ImmutableOpenMap<String, LifecyclePolicyMetadata>
                        policies = response.getPolicies(); // <1>
                }

                @Override
                public void onFailure(Exception e) {
                    // <2>
                }
            };
        // end::ilm-get-lifecycle-policy-execute-listener

        // Replace the empty listener by a blocking listener in test
        final CountDownLatch latch = new CountDownLatch(1);
        listener = new LatchedActionListener<>(listener, latch);

        // tag::ilm-get-lifecycle-policy-execute-async
        client.indexLifecycle().getLifecyclePolicyAsync(request,
            RequestOptions.DEFAULT, listener); // <1>
        // end::ilm-get-lifecycle-policy-execute-async

        assertTrue(latch.await(30L, TimeUnit.SECONDS));
    }

    public void testExplainLifecycle() throws Exception {
        RestHighLevelClient client = highLevelClient();

        // create a policy & index
        {
            Map<String, Phase> phases = new HashMap<>();
            Map<String, LifecycleAction> hotActions = new HashMap<>();
            hotActions.put(RolloverAction.NAME, new RolloverAction(
                new ByteSizeValue(50, ByteSizeUnit.GB), null, null, null));
            phases.put("hot", new Phase("hot", TimeValue.ZERO, hotActions));

            LifecyclePolicy policy = new LifecyclePolicy("my_policy",
                phases);
            PutLifecyclePolicyRequest putRequest =
                new PutLifecyclePolicyRequest(policy);
            client.indexLifecycle().putLifecyclePolicy(putRequest, RequestOptions.DEFAULT);

            CreateIndexRequest createIndexRequest = new CreateIndexRequest("my_index-1")
                .settings(Settings.builder()
                    .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
                    .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
                    .put("index.lifecycle.name", "my_policy")
                    .put("index.lifecycle.rollover_alias", "my_alias")
                    .build());
            createIndexRequest.alias(new Alias("my_alias").writeIndex(true));
            client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
            CreateIndexRequest createOtherIndexRequest = new CreateIndexRequest("other_index")
                .settings(Settings.builder()
                    .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
                    .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
                    .build());
            client.indices().create(createOtherIndexRequest, RequestOptions.DEFAULT);


            // wait for the policy to become active
            assertBusy(() -> assertNotNull(client.indexLifecycle()
                .explainLifecycle(new ExplainLifecycleRequest("my_index-1"), RequestOptions.DEFAULT)
                .getIndexResponses().get("my_index-1").getAction()));
        }

        // tag::ilm-explain-lifecycle-request
        ExplainLifecycleRequest request =
            new ExplainLifecycleRequest("my_index-1", "other_index"); // <1>
        // end::ilm-explain-lifecycle-request


        assertBusy(() -> {
            // tag::ilm-explain-lifecycle-execute
            ExplainLifecycleResponse response = client.indexLifecycle()
                .explainLifecycle(request, RequestOptions.DEFAULT);
            // end::ilm-explain-lifecycle-execute
            assertNotNull(response);

            // tag::ilm-explain-lifecycle-response
            Map<String, IndexLifecycleExplainResponse> indices =
                response.getIndexResponses();
            IndexLifecycleExplainResponse myIndex = indices.get("my_index-1");
            String policyName = myIndex.getPolicyName(); // <1>
            boolean isManaged = myIndex.managedByILM(); // <2>

            String phase = myIndex.getPhase(); // <3>
            long phaseTime = myIndex.getPhaseTime(); // <4>
            String action = myIndex.getAction(); // <5>
            long actionTime = myIndex.getActionTime();
            String step = myIndex.getStep(); // <6>
            long stepTime = myIndex.getStepTime();

            String failedStep = myIndex.getFailedStep(); // <7>
            // end::ilm-explain-lifecycle-response

            assertEquals("my_policy", policyName);
            assertTrue(isManaged);

            assertEquals("hot", phase);
            assertNotEquals(0, phaseTime);
            assertEquals("rollover", action);
            assertNotEquals(0, actionTime);
            assertEquals("check-rollover-ready", step);
            assertNotEquals(0, stepTime);

            assertNull(failedStep);

            IndexLifecycleExplainResponse otherIndex = indices.get("other_index");
            assertFalse(otherIndex.managedByILM());
            assertNull(otherIndex.getPolicyName());
            assertNull(otherIndex.getPhase());
            assertNull(otherIndex.getAction());
            assertNull(otherIndex.getStep());
            assertNull(otherIndex.getFailedStep());
            assertNull(otherIndex.getPhaseExecutionInfo());
            assertNull(otherIndex.getStepInfo());
        });

        // tag::ilm-explain-lifecycle-execute-listener
        ActionListener<ExplainLifecycleResponse> listener =
            new ActionListener<ExplainLifecycleResponse>() {
                @Override
                public void onResponse(ExplainLifecycleResponse response)
                {
                    Map<String, IndexLifecycleExplainResponse> indices =
                        response.getIndexResponses(); // <1>
                }

                @Override
                public void onFailure(Exception e) {
                    // <2>
                }
            };
        // end::ilm-explain-lifecycle-execute-listener
        final CountDownLatch latch = new CountDownLatch(1);
        listener = new LatchedActionListener<>(listener, latch);

        // tag::ilm-explain-lifecycle-execute-async
        client.indexLifecycle().explainLifecycleAsync(request,
            RequestOptions.DEFAULT, listener); // <1>
        // end::ilm-explain-lifecycle-execute-async
        assertTrue(latch.await(30L, TimeUnit.SECONDS));
    }

    public void testILMStartStopStatus() throws Exception {
        RestHighLevelClient client = highLevelClient();

        stopILM(client);

        // tag::ilm-status-request
        LifecycleManagementStatusRequest request =
            new LifecycleManagementStatusRequest();
        // end::ilm-status-request

        // Check that ILM has stopped
        {
            // tag::ilm-status-execute
            LifecycleManagementStatusResponse response =
                client.indexLifecycle()
                    .lifecycleManagementStatus(request, RequestOptions.DEFAULT);
            // end::ilm-status-execute

            // tag::ilm-status-response
            OperationMode operationMode = response.getOperationMode(); // <1>
            // end::ilm-status-response

            assertThat(operationMode, Matchers.either(equalTo(OperationMode.STOPPING)).or(equalTo(OperationMode.STOPPED)));
        }

        startILM(client);

        // tag::ilm-status-execute-listener
        ActionListener<LifecycleManagementStatusResponse> listener =
            new ActionListener<LifecycleManagementStatusResponse>() {
                @Override
                public void onResponse(
                        LifecycleManagementStatusResponse response) {
                    OperationMode operationMode = response
                        .getOperationMode(); // <1>
                }

                @Override
                public void onFailure(Exception e) {
                    // <2>
                }
            };
        // end::ilm-status-execute-listener

        final CountDownLatch latch = new CountDownLatch(1);
        listener = new LatchedActionListener<>(listener, latch);

        // tag::ilm-status-execute-async
        client.indexLifecycle().lifecycleManagementStatusAsync(request,
            RequestOptions.DEFAULT, listener); // <1>
        // end::ilm-status-execute-async
        assertTrue(latch.await(30L, TimeUnit.SECONDS));

        // Check that ILM is running again
        LifecycleManagementStatusResponse response =
            client.indexLifecycle()
                .lifecycleManagementStatus(request, RequestOptions.DEFAULT);

        OperationMode operationMode = response.getOperationMode();
        assertEquals(OperationMode.RUNNING, operationMode);
    }

    private void stopILM(RestHighLevelClient client) throws IOException, InterruptedException {
        // tag::ilm-stop-ilm-request
        StopILMRequest request = new StopILMRequest();
        // end::ilm-stop-ilm-request

        // tag::ilm-stop-ilm-execute
        AcknowledgedResponse response = client.indexLifecycle()
            .stopILM(request, RequestOptions.DEFAULT);
        // end::ilm-stop-ilm-execute

        // tag::ilm-stop-ilm-response
        boolean acknowledged = response.isAcknowledged(); // <1>
        // end::ilm-stop-ilm-response
        assertTrue(acknowledged);

        // tag::ilm-stop-ilm-execute-listener
        ActionListener<AcknowledgedResponse> listener =
            new ActionListener<AcknowledgedResponse>() {
                @Override
                public void onResponse(AcknowledgedResponse response) {
                    boolean acknowledged = response.isAcknowledged(); // <1>
                }

                @Override
                public void onFailure(Exception e) {
                    // <2>
                }
            };
        // end::ilm-stop-ilm-execute-listener

        // Replace the empty listener by a blocking listener in test
        final CountDownLatch latch = new CountDownLatch(1);
        listener = new LatchedActionListener<>(listener, latch);

        // tag::ilm-stop-ilm-execute-async
        client.indexLifecycle().stopILMAsync(request,
            RequestOptions.DEFAULT, listener); // <1>
        // end::ilm-stop-ilm-execute-async
        assertTrue(latch.await(30L, TimeUnit.SECONDS));
    }

    private void startILM(RestHighLevelClient client) throws IOException, InterruptedException {
        // tag::ilm-start-ilm-request
        StartILMRequest request1 = new StartILMRequest();
        // end::ilm-start-ilm-request

        // tag::ilm-start-ilm-execute
        AcknowledgedResponse response = client.indexLifecycle()
            .startILM(request1, RequestOptions.DEFAULT);
        // end::ilm-start-ilm-execute

        // tag::ilm-start-ilm-response
        boolean acknowledged = response.isAcknowledged(); // <1>
        // end::ilm-start-ilm-response

        assertTrue(acknowledged);

        // tag::ilm-start-ilm-execute-listener
        ActionListener<AcknowledgedResponse> listener =
            new ActionListener<AcknowledgedResponse>() {
                @Override
                public void onResponse(AcknowledgedResponse response) {
                    boolean acknowledged = response.isAcknowledged(); // <1>
                }

                @Override
                public void onFailure(Exception e) {
                    // <2>
                }
            };
        // end::ilm-start-ilm-execute-listener

        // Replace the empty listener by a blocking listener in test
        final CountDownLatch latch = new CountDownLatch(1);
        listener = new LatchedActionListener<>(listener, latch);

        // tag::ilm-start-ilm-execute-async
        client.indexLifecycle().startILMAsync(request1,
            RequestOptions.DEFAULT, listener); // <1>
        // end::ilm-start-ilm-execute-async

        assertTrue(latch.await(30L, TimeUnit.SECONDS));
    }

    public void testRetryPolicy() throws Exception {
        RestHighLevelClient client = highLevelClient();

        // setup policy to immediately fail on index
        {
            Map<String, Phase> phases = new HashMap<>();
            Map<String, LifecycleAction> warmActions = new HashMap<>();
            warmActions.put(ShrinkAction.NAME, new ShrinkAction(3, null));
            phases.put("warm", new Phase("warm", TimeValue.ZERO, warmActions));

            LifecyclePolicy policy = new LifecyclePolicy("my_policy",
                phases);
            PutLifecyclePolicyRequest putRequest =
                new PutLifecyclePolicyRequest(policy);
            client.indexLifecycle().putLifecyclePolicy(putRequest, RequestOptions.DEFAULT);

            CreateIndexRequest createIndexRequest = new CreateIndexRequest("my_index")
                .settings(Settings.builder()
                    .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 2)
                    .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
                    .put("index.lifecycle.name", "my_policy")
                    .build());
            client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
            assertBusy(() -> assertNotNull(client.indexLifecycle()
                .explainLifecycle(new ExplainLifecycleRequest("my_index"), RequestOptions.DEFAULT)
                .getIndexResponses().get("my_index").getFailedStep()), 30, TimeUnit.SECONDS);
        }

        // tag::ilm-retry-lifecycle-policy-request
        RetryLifecyclePolicyRequest request =
            new RetryLifecyclePolicyRequest("my_index"); // <1>
        // end::ilm-retry-lifecycle-policy-request


        try {
            // tag::ilm-retry-lifecycle-policy-execute
            AcknowledgedResponse response = client.indexLifecycle()
                .retryLifecyclePolicy(request, RequestOptions.DEFAULT);
            // end::ilm-retry-lifecycle-policy-execute

            // tag::ilm-retry-lifecycle-policy-response
            boolean acknowledged = response.isAcknowledged(); // <1>
            // end::ilm-retry-lifecycle-policy-response

            assertTrue(acknowledged);
        } catch (ElasticsearchException e) {
            // the retry API might fail as the shrink action steps are retryable (so if the retry API reaches ES when ILM is retrying the
            // failed `shrink` step, the retry API will fail)
            // assert that's the exception we encountered (we want to test to fail if there is an actual error with the retry api)
            assertThat(e.getMessage(), containsStringIgnoringCase("reason=cannot retry an action for an index [my_index] that has not " +
                "encountered an error when running a Lifecycle Policy"));
        }

        // tag::ilm-retry-lifecycle-policy-execute-listener
        ActionListener<AcknowledgedResponse> listener =
            new ActionListener<AcknowledgedResponse>() {
                @Override
                public void onResponse(AcknowledgedResponse response) {
                    boolean acknowledged = response.isAcknowledged(); // <1>
                }

                @Override
                public void onFailure(Exception e) {
                    // <2>
                }
            };
        // end::ilm-retry-lifecycle-policy-execute-listener

        // Replace the empty listener by a blocking listener in test
        final CountDownLatch latch = new CountDownLatch(1);
        listener = new LatchedActionListener<>(listener, latch);

        // tag::ilm-retry-lifecycle-policy-execute-async
        client.indexLifecycle().retryLifecyclePolicyAsync(request,
            RequestOptions.DEFAULT, listener); // <1>
        // end::ilm-retry-lifecycle-policy-execute-async

        assertTrue(latch.await(30L, TimeUnit.SECONDS));
    }

    public void testRemovePolicyFromIndex() throws Exception {
        RestHighLevelClient client = highLevelClient();

        // setup policy for index
        Map<String, Phase> phases = new HashMap<>();
        phases.put("delete", new Phase("delete", TimeValue.timeValueHours(10L),
            Collections.singletonMap(DeleteAction.NAME, new DeleteAction())));
        LifecyclePolicy policy = new LifecyclePolicy("my_policy", phases);
        PutLifecyclePolicyRequest putRequest = new PutLifecyclePolicyRequest(policy);
        client.indexLifecycle().putLifecyclePolicy(putRequest, RequestOptions.DEFAULT);
        CreateIndexRequest createIndexRequest = new CreateIndexRequest("my_index")
            .settings(Settings.builder()
                .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1)
                .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, 0)
                .put("index.lifecycle.name", "my_policy")
                .build());
        client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
        assertBusy(() -> assertTrue(client.indexLifecycle()
            .explainLifecycle(new ExplainLifecycleRequest("my_index"), RequestOptions.DEFAULT)
            .getIndexResponses().get("my_index").managedByILM()));

        // tag::ilm-remove-lifecycle-policy-from-index-request
        List<String> indices = new ArrayList<>();
        indices.add("my_index");
        RemoveIndexLifecyclePolicyRequest request =
            new RemoveIndexLifecyclePolicyRequest(indices); // <1>
        // end::ilm-remove-lifecycle-policy-from-index-request


        // tag::ilm-remove-lifecycle-policy-from-index-execute
        RemoveIndexLifecyclePolicyResponse response = client
            .indexLifecycle()
            .removeIndexLifecyclePolicy(request, RequestOptions.DEFAULT);
        // end::ilm-remove-lifecycle-policy-from-index-execute

        // tag::ilm-remove-lifecycle-policy-from-index-response
        boolean hasFailures = response.hasFailures(); // <1>
        List<String> failedIndexes = response.getFailedIndexes(); // <2>
        // end::ilm-remove-lifecycle-policy-from-index-response

        {
            assertFalse(hasFailures);
            Map<String, Object> indexSettings = getIndexSettings("my_index");
            assertTrue(Strings.isNullOrEmpty((String) indexSettings.get("index.lifecycle.name")));
        }

        // re-apply policy on index
        updateIndexSettings("my_index", Settings.builder().put("index.lifecycle.name", "my_policy"));
        assertBusy(() -> assertTrue(client.indexLifecycle()
            .explainLifecycle(new ExplainLifecycleRequest("my_index"), RequestOptions.DEFAULT)
            .getIndexResponses().get("my_index").managedByILM()));

        // tag::ilm-remove-lifecycle-policy-from-index-execute-listener
        ActionListener<RemoveIndexLifecyclePolicyResponse> listener =
            new ActionListener<RemoveIndexLifecyclePolicyResponse>() {
                @Override
                public void onResponse(
                        RemoveIndexLifecyclePolicyResponse response) {
                    boolean hasFailures = response.hasFailures(); // <1>
                    List<String> failedIndexes = response.getFailedIndexes();
                }

                @Override
                public void onFailure(Exception e) {
                    // <2>
                }
            };
        // end::ilm-remove-lifecycle-policy-from-index-execute-listener

        {
            Map<String, Object> indexSettings = getIndexSettings("my_index");
            assertTrue(Strings.isNullOrEmpty((String) indexSettings.get("index.lifecycle.name")));
        }

        // Replace the empty listener by a blocking listener in test
        final CountDownLatch latch = new CountDownLatch(1);
        listener = new LatchedActionListener<>(listener, latch);

        // tag::ilm-remove-lifecycle-policy-from-index-execute-async
        client.indexLifecycle().removeIndexLifecyclePolicyAsync(request,
            RequestOptions.DEFAULT, listener); // <1>
        // end::ilm-remove-lifecycle-policy-from-index-execute-async

        assertTrue(latch.await(30L, TimeUnit.SECONDS));
    }

    @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/73317")
    public void testAddSnapshotLifecyclePolicy() throws Exception {
        RestHighLevelClient client = highLevelClient();

        PutRepositoryRequest repoRequest = new PutRepositoryRequest();

        Settings.Builder settingsBuilder = Settings.builder().put("location", ".");
        repoRequest.settings(settingsBuilder);
        repoRequest.name("my_repository");
        repoRequest.type(FsRepository.TYPE);
        org.elasticsearch.action.support.master.AcknowledgedResponse response =
            client.snapshot().createRepository(repoRequest, RequestOptions.DEFAULT);
        assertTrue(response.isAcknowledged());

        //////// PUT
        // tag::slm-put-snapshot-lifecycle-policy-request
        Map<String, Object> config = new HashMap<>();
        config.put("indices", Collections.singletonList("idx"));
        SnapshotRetentionConfiguration retention =
            new SnapshotRetentionConfiguration(TimeValue.timeValueDays(30), 2, 10);
        SnapshotLifecyclePolicy policy = new SnapshotLifecyclePolicy(
            "policy_id", "name", "1 2 3 * * ?",
            "my_repository", config, retention);
        PutSnapshotLifecyclePolicyRequest request =
            new PutSnapshotLifecyclePolicyRequest(policy);
        // end::slm-put-snapshot-lifecycle-policy-request

        // tag::slm-put-snapshot-lifecycle-policy-execute
        AcknowledgedResponse resp = client.indexLifecycle()
            .putSnapshotLifecyclePolicy(request, RequestOptions.DEFAULT);
        // end::slm-put-snapshot-lifecycle-policy-execute

        // tag::slm-put-snapshot-lifecycle-policy-response
        boolean putAcknowledged = resp.isAcknowledged(); // <1>
        // end::slm-put-snapshot-lifecycle-policy-response
        assertTrue(putAcknowledged);

        // tag::slm-put-snapshot-lifecycle-policy-execute-listener
        ActionListener<AcknowledgedResponse> putListener =
                new ActionListener<AcknowledgedResponse>() {
            @Override
            public void onResponse(AcknowledgedResponse resp) {
                boolean acknowledged = resp.isAcknowledged(); // <1>
            }

            @Override
            public void onFailure(Exception e) {
                // <2>
            }
        };
        // end::slm-put-snapshot-lifecycle-policy-execute-listener

        // tag::slm-put-snapshot-lifecycle-policy-execute-async
        client.indexLifecycle().putSnapshotLifecyclePolicyAsync(request,
            RequestOptions.DEFAULT, putListener); // <1>
        // end::slm-put-snapshot-lifecycle-policy-execute-async

        //////// GET
        // tag::slm-get-snapshot-lifecycle-policy-request
        GetSnapshotLifecyclePolicyRequest getAllRequest =
            new GetSnapshotLifecyclePolicyRequest(); // <1>
        GetSnapshotLifecyclePolicyRequest getRequest =
            new GetSnapshotLifecyclePolicyRequest("policy_id"); // <2>
        // end::slm-get-snapshot-lifecycle-policy-request

        // tag::slm-get-snapshot-lifecycle-policy-execute
        GetSnapshotLifecyclePolicyResponse getResponse =
            client.indexLifecycle()
                .getSnapshotLifecyclePolicy(getRequest,
                    RequestOptions.DEFAULT);
        // end::slm-get-snapshot-lifecycle-policy-execute

        // tag::slm-get-snapshot-lifecycle-policy-execute-listener
        ActionListener<GetSnapshotLifecyclePolicyResponse> getListener =
                new ActionListener<GetSnapshotLifecyclePolicyResponse>() {
            @Override
            public void onResponse(GetSnapshotLifecyclePolicyResponse resp) {
                Map<String, SnapshotLifecyclePolicyMetadata> policies =
                    resp.getPolicies(); // <1>
            }

            @Override
            public void onFailure(Exception e) {
                // <2>
            }
        };
        // end::slm-get-snapshot-lifecycle-policy-execute-listener

        // tag::slm-get-snapshot-lifecycle-policy-execute-async
        client.indexLifecycle().getSnapshotLifecyclePolicyAsync(getRequest,
            RequestOptions.DEFAULT, getListener); // <1>
        // end::slm-get-snapshot-lifecycle-policy-execute-async

        assertThat(getResponse.getPolicies().size(), equalTo(1));
        // tag::slm-get-snapshot-lifecycle-policy-response
        SnapshotLifecyclePolicyMetadata policyMeta =
            getResponse.getPolicies().get("policy_id"); // <1>
        long policyVersion = policyMeta.getVersion();
        long policyModificationDate = policyMeta.getModifiedDate();
        long nextPolicyExecutionDate = policyMeta.getNextExecution();
        SnapshotInvocationRecord lastSuccess = policyMeta.getLastSuccess();
        SnapshotInvocationRecord lastFailure = policyMeta.getLastFailure();
        SnapshotLifecyclePolicyMetadata.SnapshotInProgress inProgress =
            policyMeta.getSnapshotInProgress();
        SnapshotLifecyclePolicy retrievedPolicy = policyMeta.getPolicy(); // <2>
        String id = retrievedPolicy.getId();
        String snapshotNameFormat = retrievedPolicy.getName();
        String repositoryName = retrievedPolicy.getRepository();
        String schedule = retrievedPolicy.getSchedule();
        Map<String, Object> snapshotConfiguration = retrievedPolicy.getConfig();
        // end::slm-get-snapshot-lifecycle-policy-response

        assertNotNull(policyMeta);
        assertThat(retrievedPolicy, equalTo(policy));

        createIndex("idx", Settings.builder().put("index.number_of_shards", 1).build());

        //////// EXECUTE
        // tag::slm-execute-snapshot-lifecycle-policy-request
        ExecuteSnapshotLifecyclePolicyRequest executeRequest =
            new ExecuteSnapshotLifecyclePolicyRequest("policy_id"); // <1>
        // end::slm-execute-snapshot-lifecycle-policy-request

        // tag::slm-execute-snapshot-lifecycle-policy-execute
        ExecuteSnapshotLifecyclePolicyResponse executeResponse =
            client.indexLifecycle()
                .executeSnapshotLifecyclePolicy(executeRequest,
                    RequestOptions.DEFAULT);
        // end::slm-execute-snapshot-lifecycle-policy-execute

        // tag::slm-execute-snapshot-lifecycle-policy-response
        final String snapshotName = executeResponse.getSnapshotName(); // <1>
        // end::slm-execute-snapshot-lifecycle-policy-response

        assertSnapshotExists(client, "my_repository", snapshotName);

        // tag::slm-execute-snapshot-lifecycle-policy-execute-listener
        ActionListener<ExecuteSnapshotLifecyclePolicyResponse> executeListener =
                new ActionListener<ExecuteSnapshotLifecyclePolicyResponse>() {
            @Override
            public void onResponse(ExecuteSnapshotLifecyclePolicyResponse r) {
                String snapshotName = r.getSnapshotName(); // <1>
            }

            @Override
            public void onFailure(Exception e) {
                // <2>
            }
        };
        // end::slm-execute-snapshot-lifecycle-policy-execute-listener

        // We need a listener that will actually wait for the snapshot to be created
        CountDownLatch latch = new CountDownLatch(1);
        executeListener =
            new ActionListener<ExecuteSnapshotLifecyclePolicyResponse>() {
                @Override
                public void onResponse(ExecuteSnapshotLifecyclePolicyResponse r) {
                    try {
                        assertSnapshotExists(client, "my_repository", r.getSnapshotName());
                    } catch (Exception e) {
                        // Ignore
                    } finally {
                        latch.countDown();
                    }
                }

                @Override
                public void onFailure(Exception e) {
                    latch.countDown();
                    fail("failed to execute slm execute: " + e);
                }
        };

        // tag::slm-execute-snapshot-lifecycle-policy-execute-async
        client.indexLifecycle()
            .executeSnapshotLifecyclePolicyAsync(executeRequest,
                RequestOptions.DEFAULT, executeListener); // <1>
        // end::slm-execute-snapshot-lifecycle-policy-execute-async
        latch.await(5, TimeUnit.SECONDS);

        // tag::slm-get-snapshot-lifecycle-stats
        GetSnapshotLifecycleStatsRequest getStatsRequest =
            new GetSnapshotLifecycleStatsRequest();
        // end::slm-get-snapshot-lifecycle-stats

        // tag::slm-get-snapshot-lifecycle-stats-execute
        GetSnapshotLifecycleStatsResponse statsResp = client.indexLifecycle()
            .getSnapshotLifecycleStats(getStatsRequest, RequestOptions.DEFAULT);
        SnapshotLifecycleStats stats = statsResp.getStats();
        SnapshotLifecycleStats.SnapshotPolicyStats policyStats =
            stats.getMetrics().get("policy_id");
        // end::slm-get-snapshot-lifecycle-stats-execute
        assertThat(
            statsResp.getStats().getMetrics().get("policy_id").getSnapshotsTaken(),
            greaterThanOrEqualTo(1L));

        //////// DELETE
        // tag::slm-delete-snapshot-lifecycle-policy-request
        DeleteSnapshotLifecyclePolicyRequest deleteRequest =
            new DeleteSnapshotLifecyclePolicyRequest("policy_id"); // <1>
        // end::slm-delete-snapshot-lifecycle-policy-request

        // tag::slm-delete-snapshot-lifecycle-policy-execute
        AcknowledgedResponse deleteResp = client.indexLifecycle()
            .deleteSnapshotLifecyclePolicy(deleteRequest, RequestOptions.DEFAULT);
        // end::slm-delete-snapshot-lifecycle-policy-execute

        // tag::slm-delete-snapshot-lifecycle-policy-response
        boolean deleteAcknowledged = deleteResp.isAcknowledged(); // <1>
        // end::slm-delete-snapshot-lifecycle-policy-response

        assertTrue(deleteResp.isAcknowledged());

        // tag::slm-delete-snapshot-lifecycle-policy-execute-listener
        ActionListener<AcknowledgedResponse> deleteListener =
            new ActionListener<AcknowledgedResponse>() {
                @Override
                public void onResponse(AcknowledgedResponse resp) {
                    boolean deleteAcknowledged = resp.isAcknowledged(); // <1>
                }

                @Override
                public void onFailure(Exception e) {
                    // <2>
                }
            };
        // end::slm-delete-snapshot-lifecycle-policy-execute-listener

        // tag::slm-delete-snapshot-lifecycle-policy-execute-async
        client.indexLifecycle()
            .deleteSnapshotLifecyclePolicyAsync(deleteRequest,
                RequestOptions.DEFAULT, deleteListener); // <1>
        // end::slm-delete-snapshot-lifecycle-policy-execute-async

        assertTrue(deleteResp.isAcknowledged());

        //////// EXECUTE RETENTION
        // tag::slm-execute-snapshot-lifecycle-retention-request
        ExecuteSnapshotLifecycleRetentionRequest req =
            new ExecuteSnapshotLifecycleRetentionRequest();
        // end::slm-execute-snapshot-lifecycle-retention-request

        // tag::slm-execute-snapshot-lifecycle-retention-execute
        AcknowledgedResponse retentionResp =
            client.indexLifecycle()
                .executeSnapshotLifecycleRetention(req,
                    RequestOptions.DEFAULT);
        // end::slm-execute-snapshot-lifecycle-retention-execute

        // tag::slm-execute-snapshot-lifecycle-retention-response
        final boolean acked = retentionResp.isAcknowledged();
        // end::slm-execute-snapshot-lifecycle-retention-response

        // tag::slm-execute-snapshot-lifecycle-retention-execute-listener
        ActionListener<AcknowledgedResponse> retentionListener =
            new ActionListener<AcknowledgedResponse>() {
                @Override
                public void onResponse(AcknowledgedResponse r) {
                    assert r.isAcknowledged(); // <1>
                }

                @Override
                public void onFailure(Exception e) {
                    // <2>
                }
            };
        // end::slm-execute-snapshot-lifecycle-retention-execute-listener

        // tag::slm-execute-snapshot-lifecycle-retention-execute-async
        client.indexLifecycle()
            .executeSnapshotLifecycleRetentionAsync(req,
                RequestOptions.DEFAULT, retentionListener); // <1>
        // end::slm-execute-snapshot-lifecycle-retention-execute-async
    }

    private void assertSnapshotExists(final RestHighLevelClient client, final String repo, final String snapshotName) throws Exception {
        assertBusy(() -> {
            GetSnapshotsRequest getSnapshotsRequest = new GetSnapshotsRequest(repo, new String[]{snapshotName});
            try {
                final GetSnapshotsResponse snaps = client.snapshot().get(getSnapshotsRequest, RequestOptions.DEFAULT);
                Optional<SnapshotInfo> info = snaps.getSnapshots().stream().findFirst();
                if (info.isPresent()) {
                    info.ifPresent(si -> {
                        assertThat(si.snapshotId().getName(), equalTo(snapshotName));
                        assertThat(si.state(), equalTo(SnapshotState.SUCCESS));
                    });
                } else {
                    fail("unable to find snapshot; " + snapshotName);
                }
            } catch (Exception e) {
                if (e.getMessage().contains("snapshot_missing_exception")) {
                    fail("snapshot does not exist: " + snapshotName);
                } else if (e.getMessage().contains("repository_exception")) {
                    fail("got a respository_exception, retrying. original message: " + e.getMessage());
                }
                throw e;
            }
        });
    }

    public void testSLMStartStopStatus() throws Exception {
        RestHighLevelClient client = highLevelClient();

        stopSLM(client);

        // tag::slm-status-request
        SnapshotLifecycleManagementStatusRequest request =
            new SnapshotLifecycleManagementStatusRequest();
        // end::slm-status-request

        // Check that SLM has stopped
        {
            // tag::slm-status-execute
            LifecycleManagementStatusResponse response =
                client.indexLifecycle()
                    .getSLMStatus(request, RequestOptions.DEFAULT);
            // end::slm-status-execute

            // tag::slm-status-response
            OperationMode operationMode = response.getOperationMode(); // <1>
            // end::slm-status-response

            assertThat(operationMode, Matchers.either(equalTo(OperationMode.STOPPING)).or(equalTo(OperationMode.STOPPED)));
        }

        startSLM(client);

        // tag::slm-status-execute-listener
        ActionListener<LifecycleManagementStatusResponse> listener =
            new ActionListener<LifecycleManagementStatusResponse>() {
                @Override
                public void onResponse(
                    LifecycleManagementStatusResponse response) {
                    OperationMode operationMode = response
                        .getOperationMode(); // <1>
                }

                @Override
                public void onFailure(Exception e) {
                    // <2>
                }
            };
        // end::slm-status-execute-listener

        final CountDownLatch latch = new CountDownLatch(1);
        listener = new LatchedActionListener<>(listener, latch);

        // tag::slm-status-execute-async
        client.indexLifecycle().getSLMStatusAsync(request,
            RequestOptions.DEFAULT, listener); // <1>
        // end::slm-status-execute-async
        assertTrue(latch.await(30L, TimeUnit.SECONDS));

        // Check that SLM is running again
        LifecycleManagementStatusResponse response =
            client.indexLifecycle()
                .getSLMStatus(request, RequestOptions.DEFAULT);

        OperationMode operationMode = response.getOperationMode();
        assertEquals(OperationMode.RUNNING, operationMode);
    }

    private void stopSLM(RestHighLevelClient client) throws IOException, InterruptedException {
        // tag::slm-stop-slm-request
        StopSLMRequest request = new StopSLMRequest();
        // end::slm-stop-slm-request

        // tag::slm-stop-slm-execute
        AcknowledgedResponse response = client.indexLifecycle()
            .stopSLM(request, RequestOptions.DEFAULT);
        // end::slm-stop-slm-execute

        // tag::slm-stop-slm-response
        boolean acknowledged = response.isAcknowledged(); // <1>
        // end::slm-stop-slm-response
        assertTrue(acknowledged);

        // tag::slm-stop-slm-execute-listener
        ActionListener<AcknowledgedResponse> listener =
            new ActionListener<AcknowledgedResponse>() {
                @Override
                public void onResponse(AcknowledgedResponse response) {
                    boolean acknowledged = response.isAcknowledged(); // <1>
                }

                @Override
                public void onFailure(Exception e) {
                    // <2>
                }
            };
        // end::slm-stop-slm-execute-listener

        // Replace the empty listener by a blocking listener in test
        final CountDownLatch latch = new CountDownLatch(1);
        listener = new LatchedActionListener<>(listener, latch);

        // tag::slm-stop-slm-execute-async
        client.indexLifecycle().stopSLMAsync(request,
            RequestOptions.DEFAULT, listener); // <1>
        // end::slm-stop-slm-execute-async
        assertTrue(latch.await(30L, TimeUnit.SECONDS));
    }

    private void startSLM(RestHighLevelClient client) throws IOException, InterruptedException {
        // tag::slm-start-slm-request
        StartSLMRequest request1 = new StartSLMRequest();
        // end::slm-start-slm-request

        // tag::slm-start-slm-execute
        AcknowledgedResponse response = client.indexLifecycle()
            .startSLM(request1, RequestOptions.DEFAULT);
        // end::slm-start-slm-execute

        // tag::slm-start-slm-response
        boolean acknowledged = response.isAcknowledged(); // <1>
        // end::slm-start-slm-response

        assertTrue(acknowledged);

        // tag::slm-start-slm-execute-listener
        ActionListener<AcknowledgedResponse> listener =
            new ActionListener<AcknowledgedResponse>() {
                @Override
                public void onResponse(AcknowledgedResponse response) {
                    boolean acknowledged = response.isAcknowledged(); // <1>
                }

                @Override
                public void onFailure(Exception e) {
                    // <2>
                }
            };
        // end::slm-start-slm-execute-listener

        // Replace the empty listener by a blocking listener in test
        final CountDownLatch latch = new CountDownLatch(1);
        listener = new LatchedActionListener<>(listener, latch);

        // tag::slm-start-slm-execute-async
        client.indexLifecycle().startSLMAsync(request1,
            RequestOptions.DEFAULT, listener); // <1>
        // end::slm-start-slm-execute-async

        assertTrue(latch.await(30L, TimeUnit.SECONDS));
    }

}
