Skip to content

Commit

Permalink
Add Clone Index API (#44267)
Browse files Browse the repository at this point in the history
Adds an API to clone an index. This is similar to the index split and shrink APIs, just with the
difference that the number of primary shards is kept the same. In case where the filesystem
provides hard-linking capabilities, this is a very cheap operation.

Indexing cloning can be done by running `POST my_source_index/_clone/my_target_index` and it
supports the same options as the split and shrink APIs.

Closes #44128
  • Loading branch information
ywelsch committed Jul 25, 2019
1 parent 729aca5 commit 76fcc81
Show file tree
Hide file tree
Showing 23 changed files with 844 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -909,6 +909,33 @@ public void splitAsync(ResizeRequest resizeRequest, RequestOptions options, Acti
ResizeResponse::fromXContent, listener, emptySet());
}

/**
* Clones an index using the Clone Index API.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-clone-index.html">
* Clone Index API on elastic.co</a>
* @param resizeRequest the request
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return the response
* @throws IOException in case there is a problem sending the request or parsing back the response
*/
public ResizeResponse clone(ResizeRequest resizeRequest, RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(resizeRequest, IndicesRequestConverters::clone, options,
ResizeResponse::fromXContent, emptySet());
}

/**
* Asynchronously clones an index using the Clone Index API.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-clone-index.html">
* Clone Index API on elastic.co</a>
* @param resizeRequest the request
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @param listener the listener to be notified upon request completion
*/
public void cloneAsync(ResizeRequest resizeRequest, RequestOptions options, ActionListener<ResizeResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(resizeRequest, IndicesRequestConverters::clone, options,
ResizeResponse::fromXContent, listener, emptySet());
}

/**
* Rolls over an index using the Rollover Index API.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-rollover-index.html">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,13 @@ static Request shrink(ResizeRequest resizeRequest) throws IOException {
return resize(resizeRequest);
}

static Request clone(ResizeRequest resizeRequest) throws IOException {
if (resizeRequest.getResizeType() != ResizeType.CLONE) {
throw new IllegalArgumentException("Wrong resize type [" + resizeRequest.getResizeType() + "] for indices clone request");
}
return resize(resizeRequest);
}

private static Request resize(ResizeRequest resizeRequest) throws IOException {
String endpoint = new RequestConverters.EndpointBuilder().addPathPart(resizeRequest.getSourceIndex())
.addPathPartAsIs("_" + resizeRequest.getResizeType().name().toLowerCase(Locale.ROOT))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,30 @@ public void testSplit() throws IOException {
assertNotNull(aliasData);
}

@SuppressWarnings("unchecked")
public void testClone() throws IOException {
createIndex("source", Settings.builder().put("index.number_of_shards", 2).put("index.number_of_replicas", 0)
.put("index.number_of_routing_shards", 4).build());
updateIndexSettings("source", Settings.builder().put("index.blocks.write", true));

ResizeRequest resizeRequest = new ResizeRequest("target", "source");
resizeRequest.setResizeType(ResizeType.CLONE);
Settings targetSettings = Settings.builder().put("index.number_of_shards", 2).put("index.number_of_replicas", 0).build();
resizeRequest.setTargetIndex(new org.elasticsearch.action.admin.indices.create.CreateIndexRequest("target")
.settings(targetSettings)
.alias(new Alias("alias")));
ResizeResponse resizeResponse = execute(resizeRequest, highLevelClient().indices()::clone, highLevelClient().indices()::cloneAsync);
assertTrue(resizeResponse.isAcknowledged());
assertTrue(resizeResponse.isShardsAcknowledged());
Map<String, Object> getIndexResponse = getAsMap("target");
Map<String, Object> indexSettings = (Map<String, Object>)XContentMapValues.extractValue("target.settings.index", getIndexResponse);
assertNotNull(indexSettings);
assertEquals("2", indexSettings.get("number_of_shards"));
assertEquals("0", indexSettings.get("number_of_replicas"));
Map<String, Object> aliasData = (Map<String, Object>)XContentMapValues.extractValue("target.aliases.alias", getIndexResponse);
assertNotNull(aliasData);
}

public void testRollover() throws IOException {
highLevelClient().indices().create(new CreateIndexRequest("test").alias(new Alias("alias")), RequestOptions.DEFAULT);
RolloverRequest rolloverRequest = new RolloverRequest("alias", "test_new");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -830,18 +830,33 @@ public void testSplit() throws IOException {

public void testSplitWrongResizeType() {
ResizeRequest resizeRequest = new ResizeRequest("target", "source");
resizeRequest.setResizeType(ResizeType.SHRINK);
ResizeType wrongType = randomFrom(ResizeType.SHRINK, ResizeType.CLONE);
resizeRequest.setResizeType(wrongType);
IllegalArgumentException iae = LuceneTestCase.expectThrows(IllegalArgumentException.class, ()
-> IndicesRequestConverters.split(resizeRequest));
Assert.assertEquals("Wrong resize type [SHRINK] for indices split request", iae.getMessage());
Assert.assertEquals("Wrong resize type [" + wrongType.name() + "] for indices split request", iae.getMessage());
}

public void testClone() throws IOException {
resizeTest(ResizeType.CLONE, IndicesRequestConverters::clone);
}

public void testCloneWrongResizeType() {
ResizeRequest resizeRequest = new ResizeRequest("target", "source");
ResizeType wrongType = randomFrom(ResizeType.SHRINK, ResizeType.SPLIT);
resizeRequest.setResizeType(wrongType);
IllegalArgumentException iae = LuceneTestCase.expectThrows(IllegalArgumentException.class, ()
-> IndicesRequestConverters.clone(resizeRequest));
Assert.assertEquals("Wrong resize type [" + wrongType.name() + "] for indices clone request", iae.getMessage());
}

public void testShrinkWrongResizeType() {
ResizeRequest resizeRequest = new ResizeRequest("target", "source");
resizeRequest.setResizeType(ResizeType.SPLIT);
ResizeType wrongType = randomFrom(ResizeType.SPLIT, ResizeType.CLONE);
resizeRequest.setResizeType(wrongType);
IllegalArgumentException iae = LuceneTestCase.expectThrows(IllegalArgumentException.class, ()
-> IndicesRequestConverters.shrink(resizeRequest));
Assert.assertEquals("Wrong resize type [SPLIT] for indices shrink request", iae.getMessage());
Assert.assertEquals("Wrong resize type [" + wrongType.name() + "] for indices shrink request", iae.getMessage());
}

public void testShrink() throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1808,6 +1808,75 @@ public void onFailure(Exception e) {
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}

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

{
createIndex("source_index", Settings.builder().put("index.number_of_shards", 2).put("index.number_of_replicas", 0).build());
updateIndexSettings("source_index", Settings.builder().put("index.blocks.write", true));
}

// tag::clone-index-request
ResizeRequest request = new ResizeRequest("target_index","source_index"); // <1>
request.setResizeType(ResizeType.CLONE); // <2>
// end::clone-index-request

// tag::clone-index-request-timeout
request.timeout(TimeValue.timeValueMinutes(2)); // <1>
request.timeout("2m"); // <2>
// end::clone-index-request-timeout
// tag::clone-index-request-masterTimeout
request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); // <1>
request.masterNodeTimeout("1m"); // <2>
// end::clone-index-request-masterTimeout
// tag::clone-index-request-waitForActiveShards
request.setWaitForActiveShards(2); // <1>
request.setWaitForActiveShards(ActiveShardCount.DEFAULT); // <2>
// end::clone-index-request-waitForActiveShards
// tag::clone-index-request-settings
request.getTargetIndexRequest().settings(Settings.builder()
.put("index.number_of_shards", 2)); // <1>
// end::clone-index-request-settings
// tag::clone-index-request-aliases
request.getTargetIndexRequest().alias(new Alias("target_alias")); // <1>
// end::clone-index-request-aliases

// tag::clone-index-execute
ResizeResponse resizeResponse = client.indices().clone(request, RequestOptions.DEFAULT);
// end::clone-index-execute

// tag::clone-index-response
boolean acknowledged = resizeResponse.isAcknowledged(); // <1>
boolean shardsAcked = resizeResponse.isShardsAcknowledged(); // <2>
// end::clone-index-response
assertTrue(acknowledged);
assertTrue(shardsAcked);

// tag::clone-index-execute-listener
ActionListener<ResizeResponse> listener = new ActionListener<ResizeResponse>() {
@Override
public void onResponse(ResizeResponse resizeResponse) {
// <1>
}

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

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

// tag::clone-index-execute-async
client.indices().cloneAsync(request, RequestOptions.DEFAULT,listener); // <1>
// end::clone-index-execute-async

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

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

Expand Down
80 changes: 80 additions & 0 deletions docs/java-rest/high-level/indices/clone_index.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
--
:api: clone-index
:request: ResizeRequest
:response: ResizeResponse
--

[id="{upid}-{api}"]
=== Clone Index API

[id="{upid}-{api}-request"]
==== Resize Request

The Clone Index API requires a +{request}+ instance.
A +{request}+ requires two string arguments:

["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-request]
--------------------------------------------------
<1> The target index (first argument) to clone the source index (second argument) into
<2> The resize type needs to be set to `CLONE`

==== Optional arguments
The following arguments can optionally be provided:

["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-request-timeout]
--------------------------------------------------
<1> Timeout to wait for the all the nodes to acknowledge the index is opened
as a `TimeValue`
<2> Timeout to wait for the all the nodes to acknowledge the index is opened
as a `String`

["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-request-masterTimeout]
--------------------------------------------------
<1> Timeout to connect to the master node as a `TimeValue`
<2> Timeout to connect to the master node as a `String`

["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-request-waitForActiveShards]
--------------------------------------------------
<1> The number of active shard copies to wait for before the clone index API
returns a response, as an `int`
<2> The number of active shard copies to wait for before the clone index API
returns a response, as an `ActiveShardCount`

["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-request-settings]
--------------------------------------------------
<1> The settings to apply to the target index, which optionally include the
number of shards to create for it

["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-request-aliases]
--------------------------------------------------
<1> The aliases to associate the target index with

include::../execution.asciidoc[]

[id="{upid}-{api}-response"]
==== Clone Index Response

The returned +{response}+ allows to retrieve information about the
executed operation as follows:

["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-response]
--------------------------------------------------
<1> Indicates whether all of the nodes have acknowledged the request
<2> Indicates whether the requisite number of shard copies were started for
each shard in the index before timing out


2 changes: 2 additions & 0 deletions docs/java-rest/high-level/supported-apis.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Index Management::
* <<{upid}-close-index>>
* <<{upid}-shrink-index>>
* <<{upid}-split-index>>
* <<{upid}-clone-index>>
* <<{upid}-refresh>>
* <<{upid}-flush>>
* <<{upid}-flush-synced>>
Expand Down Expand Up @@ -133,6 +134,7 @@ include::indices/open_index.asciidoc[]
include::indices/close_index.asciidoc[]
include::indices/shrink_index.asciidoc[]
include::indices/split_index.asciidoc[]
include::indices/clone_index.asciidoc[]
include::indices/refresh.asciidoc[]
include::indices/flush.asciidoc[]
include::indices/flush_synced.asciidoc[]
Expand Down
3 changes: 3 additions & 0 deletions docs/reference/indices.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ index settings, aliases, mappings, and index templates.
* <<indices-open-close>>
* <<indices-shrink-index>>
* <<indices-split-index>>
* <<indices-clone-index>>
* <<indices-rollover-index>>
* <<freeze-index-api>>
* <<unfreeze-index-api>>
Expand Down Expand Up @@ -72,6 +73,8 @@ include::indices/shrink-index.asciidoc[]

include::indices/split-index.asciidoc[]

include::indices/clone-index.asciidoc[]

include::indices/rollover-index.asciidoc[]

include::indices/apis/freeze.asciidoc[]
Expand Down
Loading

0 comments on commit 76fcc81

Please sign in to comment.