Skip to content

ILM: add searchable snapshot action #52585

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

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
25cea2b
ILM: add searchable snapshot action
andreidan Feb 19, 2020
3c32490
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Feb 21, 2020
7dbc46e
Add license header
andreidan Feb 21, 2020
9881c44
Document why we don’t restore the lifecycle setting
andreidan Feb 21, 2020
a601dbf
Checkstyle
andreidan Feb 21, 2020
f0a9da1
Remove unused import
andreidan Feb 21, 2020
dfec7c9
Use WaitForIndexColorStep
andreidan Feb 24, 2020
a6bc720
Use requireNonNull
andreidan Feb 24, 2020
271fb54
Refactorings and cleanups
andreidan Feb 25, 2020
f6f7d29
Make performAction final in AsyncRetryDuringSnapshotActionStep
andreidan Feb 25, 2020
7075752
Add docs and redesign OnAsyncWaitBranchingStep
andreidan Feb 26, 2020
501dbaf
Rename BranchingStepListener onStopWaitingForCondition
andreidan Feb 27, 2020
1222902
Extract and reuse the alias swap request
andreidan Feb 28, 2020
faa2834
Rename restoredIndexPrefix to targetIndexPrefix
andreidan Mar 2, 2020
21d67cb
Fix tests
andreidan Mar 2, 2020
7e0d5f7
Unused imports
andreidan Mar 2, 2020
eafe3de
Handle null next step key
andreidan Mar 2, 2020
bc91ae3
Try with resources in test
andreidan Mar 2, 2020
8520c7b
Rename originalIndex to sourceIndexName
andreidan Mar 2, 2020
9735649
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 2, 2020
d20cf52
More tests
andreidan Mar 2, 2020
8a46d05
Add more steps tests
andreidan Mar 3, 2020
12c1f5f
Make method static
andreidan Mar 3, 2020
3870388
Don’t fail if the response isAck flag is false
andreidan Mar 3, 2020
d28557c
Searchable action tests and more step tests
andreidan Mar 4, 2020
357987b
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 4, 2020
6960187
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 5, 2020
9af943f
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 6, 2020
858e6a1
Store the snapshot name in the ILM execution state
andreidan Mar 6, 2020
6edfcd0
Remove import
andreidan Mar 6, 2020
e4791b0
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 6, 2020
63194ea
Copy the repository and snapshot name
andreidan Mar 10, 2020
a92463f
Expose repo and snapshot name in the ILM explain response
andreidan Mar 10, 2020
0f65ed7
Explain lifecycle repo and snapshot in transport
andreidan Mar 10, 2020
6e779b6
Delete action has an optional parameter delege_generated_snapshot
andreidan Mar 10, 2020
841f6bc
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 10, 2020
da361b5
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
andreidan Mar 11, 2020
5eb524e
Drop the searchable repository action param
andreidan Mar 11, 2020
8e8ff99
Drop sys.out.println
andreidan Mar 11, 2020
119c439
Fix docs tests
andreidan Mar 11, 2020
064b4e7
BWC for DeleteAction delete generated snap flag
andreidan Mar 11, 2020
1cb6480
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 11, 2020
3a750e0
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 12, 2020
0a79753
Update settings version when copying settings
andreidan Mar 12, 2020
013d4df
Add test that deletes the snapshot
andreidan Mar 12, 2020
aa098d2
Document step
andreidan Mar 12, 2020
520d6bb
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 12, 2020
5e4cf55
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
andreidan Mar 24, 2020
32e3371
Format code
andreidan Mar 24, 2020
ac9e9c3
Make CleanupSnapshotStep retryable
andreidan Mar 24, 2020
b68b61c
Fix typo
andreidan Mar 24, 2020
374e50d
Format code
andreidan Mar 25, 2020
4f25ce9
CreateSnapshotStep request specifies index name
andreidan Mar 24, 2020
0f6f371
Rename delete action field to delete_searchable_snapshot
andreidan Mar 25, 2020
15d39ed
Rename SSA repo field to snapshot_repository (underscore)
andreidan Mar 25, 2020
e76ff6c
Fix delete action test assertions
andreidan Mar 25, 2020
ff7623f
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 25, 2020
3c56f73
Fix doc tests
andreidan Mar 25, 2020
06bf054
Change debug level to warn
andreidan Mar 25, 2020
c3d785e
Log when we stop waiting for the branch condition
andreidan Mar 25, 2020
9d37f78
MountSnapshotStep is retries when response is not acked
andreidan Mar 25, 2020
985477e
Doc null handling
andreidan Mar 25, 2020
2e4bd2b
Update info message to be good english
andreidan Mar 25, 2020
cd73bd6
Use the correct Info class Luke
andreidan Mar 25, 2020
0ca1db0
Cleanup if/else branches that check the snapshot state
andreidan Mar 25, 2020
2b467cf
Log that the swap aliases response was not acked
andreidan Mar 25, 2020
5f506ee
SnapshotInProgress null check
andreidan Mar 25, 2020
80c9a45
Fix test
andreidan Mar 25, 2020
8ed70c8
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 26, 2020
c38e081
Delete phase min_age after 5 seconds
andreidan Mar 26, 2020
c032d3a
Checkstyle
andreidan Mar 26, 2020
7107b1a
Use underscore in action name too
andreidan Mar 26, 2020
5bc6df2
Get the generated snapshot name from the original index in test
andreidan Mar 27, 2020
5b7f599
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 27, 2020
7f1940d
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 27, 2020
879e7d6
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 27, 2020
01222af
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 30, 2020
64a903d
Update exception message
andreidan Mar 30, 2020
6e4ede4
Update javadoc
andreidan Mar 30, 2020
864f4c3
Add example of generated snapshot name
andreidan Mar 30, 2020
9bbb9df
Fail CleanupSnapshotStep if the request is not acked
andreidan Mar 30, 2020
2c61f00
Move empty settings check before target index check
andreidan Mar 30, 2020
ac574b2
Delete WaitForSnapshotInProgressStepTests
andreidan Mar 31, 2020
567d067
Merge branch 'feature/searchable-snapshots' into ilm-searchable-snaps…
elasticmachine Mar 31, 2020
b766806
Fix typo
andreidan Mar 31, 2020
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
Expand Up @@ -53,6 +53,7 @@
import org.elasticsearch.xpack.core.ilm.LifecycleType;
import org.elasticsearch.xpack.core.ilm.ReadOnlyAction;
import org.elasticsearch.xpack.core.ilm.RolloverAction;
import org.elasticsearch.xpack.core.ilm.SearchableSnapshotAction;
import org.elasticsearch.xpack.core.ilm.SetPriorityAction;
import org.elasticsearch.xpack.core.ilm.ShrinkAction;
import org.elasticsearch.xpack.core.ilm.TimeseriesLifecycleType;
Expand Down Expand Up @@ -550,6 +551,7 @@ public List<NamedWriteableRegistry.Entry> getNamedWriteables() {
new NamedWriteableRegistry.Entry(LifecycleAction.class, SetPriorityAction.NAME, SetPriorityAction::new),
new NamedWriteableRegistry.Entry(LifecycleAction.class, UnfollowAction.NAME, UnfollowAction::new),
new NamedWriteableRegistry.Entry(LifecycleAction.class, WaitForSnapshotAction.NAME, WaitForSnapshotAction::new),
new NamedWriteableRegistry.Entry(LifecycleAction.class, SearchableSnapshotAction.NAME, SearchableSnapshotAction::new),
// Transforms
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.TRANSFORM, TransformFeatureSetUsage::new),
new NamedWriteableRegistry.Entry(PersistentTaskParams.class, TransformField.TASK_NAME, TransformTaskParams::new),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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.
*/
package org.elasticsearch.xpack.core.ilm;

import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateObserver;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.Strings;
import org.elasticsearch.snapshots.SnapshotMissingException;

import static org.elasticsearch.xpack.core.ilm.LifecycleExecutionState.fromIndexMetadata;

/**
* Deletes the snapshot designated by the snapshot name present in the lifecycle execution state, hosted in the configured repository.
*/
public class CleanupSnapshotStep extends AsyncActionStep {
public static final String NAME = "cleanup-snapshot";

private final String snapshotRepository;

public CleanupSnapshotStep(StepKey key, StepKey nextStepKey, Client client, String snapshotRepository) {
super(key, nextStepKey, client);
this.snapshotRepository = snapshotRepository;
}

@Override
public boolean isRetryable() {
return true;
}

@Override
public void performAction(IndexMetaData indexMetaData, ClusterState currentClusterState, ClusterStateObserver observer,
Listener listener) {
final String indexName = indexMetaData.getIndex().getName();

LifecycleExecutionState lifecycleState = fromIndexMetadata(indexMetaData);
final String snapshotName = lifecycleState.getSnapshotName();
if (Strings.hasText(snapshotName) == false) {
String policyName = indexMetaData.getSettings().get(LifecycleSettings.LIFECYCLE_NAME);
listener.onFailure(
new IllegalStateException("snapshot name was not generated for policy [" + policyName + "] and index [" + indexName + "]")
);
return;
}
DeleteSnapshotRequest deleteSnapshotRequest = new DeleteSnapshotRequest(snapshotRepository, snapshotName);
getClient().admin().cluster().deleteSnapshot(deleteSnapshotRequest, new ActionListener<>() {

@Override
public void onResponse(AcknowledgedResponse acknowledgedResponse) {
listener.onResponse(true);
}

@Override
public void onFailure(Exception e) {
if (e instanceof SnapshotMissingException) {
// during the happy flow we generate a snapshot name and that snapshot doesn't exist in the repository
listener.onResponse(true);
} else {
listener.onFailure(e);
}
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,32 @@

/**
* Copies the execution state data from one index to another, typically after a
* new index has been created. Useful for actions such as shrink.
* new index has been created. As part of the execution state copy it will set the target index
* "current step" to the provided step name (part of the same phase and action as the current step's, unless
* the "complete" step is configured in which case the action will be changed to "complete" as well)
*
* Useful for actions such as shrink.
*/
public class CopyExecutionStateStep extends ClusterStateActionStep {
public static final String NAME = "copy-execution-state";

private static final Logger logger = LogManager.getLogger(CopyExecutionStateStep.class);

private String shrunkIndexPrefix;
private final String targetIndexPrefix;
private final String targetNextStepName;


public CopyExecutionStateStep(StepKey key, StepKey nextStepKey, String shrunkIndexPrefix) {
public CopyExecutionStateStep(StepKey key, StepKey nextStepKey, String targetIndexPrefix, String targetNextStepName) {
super(key, nextStepKey);
this.shrunkIndexPrefix = shrunkIndexPrefix;
this.targetIndexPrefix = targetIndexPrefix;
this.targetNextStepName = targetNextStepName;
}

String getTargetIndexPrefix() {
return targetIndexPrefix;
}

String getShrunkIndexPrefix() {
return shrunkIndexPrefix;
String getTargetNextStepName() {
return targetNextStepName;
}

@Override
Expand All @@ -48,8 +57,8 @@ public ClusterState performAction(Index index, ClusterState clusterState) {
}
// get source index
String indexName = indexMetaData.getIndex().getName();
// get target shrink index
String targetIndexName = shrunkIndexPrefix + indexName;
// get target index
String targetIndexName = targetIndexPrefix + indexName;
IndexMetaData targetIndexMetaData = clusterState.metaData().index(targetIndexName);

if (targetIndexMetaData == null) {
Expand All @@ -67,8 +76,12 @@ public ClusterState performAction(Index index, ClusterState clusterState) {
LifecycleExecutionState.Builder relevantTargetCustomData = LifecycleExecutionState.builder();
relevantTargetCustomData.setIndexCreationDate(lifecycleDate);
relevantTargetCustomData.setPhase(phase);
relevantTargetCustomData.setAction(action);
relevantTargetCustomData.setStep(ShrunkenIndexCheckStep.NAME);
relevantTargetCustomData.setStep(targetNextStepName);
if (targetNextStepName.equals(PhaseCompleteStep.NAME)) {
relevantTargetCustomData.setAction(PhaseCompleteStep.NAME);
} else {
relevantTargetCustomData.setAction(action);
}

MetaData.Builder newMetaData = MetaData.builder(clusterState.getMetaData())
.put(IndexMetaData.builder(targetIndexMetaData)
Expand All @@ -79,15 +92,22 @@ public ClusterState performAction(Index index, ClusterState clusterState) {

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
CopyExecutionStateStep that = (CopyExecutionStateStep) o;
return Objects.equals(shrunkIndexPrefix, that.shrunkIndexPrefix);
return Objects.equals(targetIndexPrefix, that.targetIndexPrefix) &&
Objects.equals(targetNextStepName, that.targetNextStepName);
}

@Override
public int hashCode() {
return Objects.hash(super.hashCode(), shrunkIndexPrefix);
return Objects.hash(super.hashCode(), targetIndexPrefix, targetNextStepName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.
*/
package org.elasticsearch.xpack.core.ilm;

import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateObserver;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.settings.Settings;

import java.util.Objects;

/**
* Copy the provided settings from the source to the target index.
* <p>
* The target index is derived from the source index using the provided prefix.
* This is useful for actions like shrink or searchable snapshot that create a new index and migrate the ILM execution from the source
* to the target index.
*/
public class CopySettingsStep extends AsyncActionStep {
public static final String NAME = "copy-settings";

private final String[] settingsKeys;
private final String indexPrefix;

public CopySettingsStep(StepKey key, StepKey nextStepKey, Client client, String indexPrefix, String... settingsKeys) {
super(key, nextStepKey, client);
Objects.nonNull(indexPrefix);
Objects.nonNull(settingsKeys);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need to be Objects.requireNonNull, currently it just returns true or false but doesn't throw an exception.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch (I hadn't even acknowledged the existence of an API that just does obj != null - TIL :) )

this.indexPrefix = indexPrefix;
this.settingsKeys = settingsKeys;
}

@Override
public boolean isRetryable() {
return true;
}

@Override
public void performAction(IndexMetaData indexMetaData, ClusterState currentState, ClusterStateObserver observer, Listener listener) {
String indexName = indexPrefix + indexMetaData.getIndex().getName();

Settings.Builder settings = Settings.builder();
for (String key : settingsKeys) {
String value = indexMetaData.getSettings().get(key);
settings.put(key, value);
}

UpdateSettingsRequest updateSettingsRequest = new UpdateSettingsRequest(indexName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently we do this as an AsyncActionStep, but should we rather do this as a ClusterStateActionStep to avoid having to actually issue the request and wait for it? I believe it would be a bit better then, and it would also allow us to copy settings that may not be settable (if we need that functionality in the future)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh interesting. I agree it'd be a better avenue

.masterNodeTimeout(getMasterTimeout(currentState))
.settings(settings);
getClient().admin().indices().updateSettings(updateSettingsRequest,
ActionListener.wrap(response -> listener.onResponse(true), listener::onFailure));
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
CopySettingsStep that = (CopySettingsStep) o;
return Objects.equals(settingsKeys, that.settingsKeys) &&
Objects.equals(indexPrefix, that.indexPrefix);
}

@Override
public int hashCode() {
return Objects.hash(super.hashCode(), settingsKeys, indexPrefix);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* 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.
*/
package org.elasticsearch.xpack.core.ilm;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.index.Index;

import java.util.Collections;
import java.util.List;
import java.util.Locale;

import static org.elasticsearch.xpack.core.ilm.LifecycleExecutionState.ILM_CUSTOM_METADATA_KEY;
import static org.elasticsearch.xpack.core.ilm.LifecycleExecutionState.fromIndexMetadata;

/**
* Generates a snapshot name for the given index and records it in the index metadata.
*/
public class GenerateSnapshotNameStep extends ClusterStateActionStep {

public static final String NAME = "generate-snapshot-name";

private static final Logger logger = LogManager.getLogger(TakeSnapshotStep.class);

private static final IndexNameExpressionResolver.DateMathExpressionResolver DATE_MATH_RESOLVER =
new IndexNameExpressionResolver.DateMathExpressionResolver();

public GenerateSnapshotNameStep(StepKey key, StepKey nextStepKey) {
super(key, nextStepKey);
}

@Override
public ClusterState performAction(Index index, ClusterState clusterState) {
IndexMetaData indexMetaData = clusterState.metaData().index(index);
if (indexMetaData == null) {
// Index must have been since deleted, ignore it
logger.debug("[{}] lifecycle action for index [{}] executed but index no longer exists", getKey().getAction(), index.getName());
return clusterState;
}

ClusterState.Builder newClusterStateBuilder = ClusterState.builder(clusterState);

LifecycleExecutionState lifecycleState = fromIndexMetadata(indexMetaData);
assert lifecycleState.getSnapshotName() == null : "index " + index.getName() + " should not have a snapshot generated by " +
"the ilm policy but has " + lifecycleState.getSnapshotName();
LifecycleExecutionState.Builder newCustomData = LifecycleExecutionState.builder(lifecycleState);
String policy = indexMetaData.getSettings().get(LifecycleSettings.LIFECYCLE_NAME);
String snapshotName = generateSnapshotName(generateSnapshotName("<{now/M}-" + index.getName() + "-" + policy + ">"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we generating the snapshot name twice here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, do we think {now/M} is enough granularity? We could probably go with /d to be a bit more granular?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this should use the same validation that SnapshotLifecyclePolicy.validate() does for validating things (for example, no # in the name). We should factor that validation out into a method and use it here.

newCustomData.setSnapshotName(snapshotName);

IndexMetaData.Builder indexMetadataBuilder = IndexMetaData.builder(indexMetaData);
indexMetadataBuilder.putCustom(ILM_CUSTOM_METADATA_KEY, newCustomData.build().asMap());
newClusterStateBuilder.metaData(MetaData.builder(clusterState.getMetaData()).put(indexMetadataBuilder));
return newClusterStateBuilder.build();
}

/**
* Since snapshots need to be uniquely named, this method will resolve any date math used in
* the provided name, as well as appending a unique identifier so expressions that may overlap
* still result in unique snapshot names.
*/
public static String generateSnapshotName(String name) {
return generateSnapshotName(name, new ResolverContext());
}

public static String generateSnapshotName(String name, IndexNameExpressionResolver.Context context) {
List<String> candidates = DATE_MATH_RESOLVER.resolve(context, Collections.singletonList(name));
if (candidates.size() != 1) {
throw new IllegalStateException("resolving snapshot name " + name + " generated more than one candidate: " + candidates);
}
// TODO: we are breaking the rules of UUIDs by lowercasing this here, find an alternative (snapshot names must be lowercase)
return (candidates.get(0) + "-" + UUIDs.randomBase64UUID()).toLowerCase(Locale.ROOT);
}

/**
* This is a context for the DateMathExpressionResolver, which does not require
* {@code IndicesOptions} or {@code ClusterState} since it only uses the start
* time to resolve expressions
*/
public static final class ResolverContext extends IndexNameExpressionResolver.Context {
public ResolverContext() {
this(System.currentTimeMillis());
}

public ResolverContext(long startTime) {
super(null, null, startTime, false, false);
}

@Override
public ClusterState getState() {
throw new UnsupportedOperationException("should never be called");
}

@Override
public IndicesOptions getOptions() {
throw new UnsupportedOperationException("should never be called");
}
}

}
Loading