Skip to content

Commit 91c35aa

Browse files
ywelschjkakavas
authored andcommitted
Add Clone Index API (#44267)
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
1 parent d420bc4 commit 91c35aa

File tree

23 files changed

+844
-17
lines changed

23 files changed

+844
-17
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java

+27
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,33 @@ public void splitAsync(ResizeRequest resizeRequest, RequestOptions options, Acti
909909
ResizeResponse::fromXContent, listener, emptySet());
910910
}
911911

912+
/**
913+
* Clones an index using the Clone Index API.
914+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-clone-index.html">
915+
* Clone Index API on elastic.co</a>
916+
* @param resizeRequest the request
917+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
918+
* @return the response
919+
* @throws IOException in case there is a problem sending the request or parsing back the response
920+
*/
921+
public ResizeResponse clone(ResizeRequest resizeRequest, RequestOptions options) throws IOException {
922+
return restHighLevelClient.performRequestAndParseEntity(resizeRequest, IndicesRequestConverters::clone, options,
923+
ResizeResponse::fromXContent, emptySet());
924+
}
925+
926+
/**
927+
* Asynchronously clones an index using the Clone Index API.
928+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-clone-index.html">
929+
* Clone Index API on elastic.co</a>
930+
* @param resizeRequest the request
931+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
932+
* @param listener the listener to be notified upon request completion
933+
*/
934+
public void cloneAsync(ResizeRequest resizeRequest, RequestOptions options, ActionListener<ResizeResponse> listener) {
935+
restHighLevelClient.performRequestAsyncAndParseEntity(resizeRequest, IndicesRequestConverters::clone, options,
936+
ResizeResponse::fromXContent, listener, emptySet());
937+
}
938+
912939
/**
913940
* Rolls over an index using the Rollover Index API.
914941
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-rollover-index.html">

client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesRequestConverters.java

+7
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,13 @@ static Request shrink(ResizeRequest resizeRequest) throws IOException {
337337
return resize(resizeRequest);
338338
}
339339

340+
static Request clone(ResizeRequest resizeRequest) throws IOException {
341+
if (resizeRequest.getResizeType() != ResizeType.CLONE) {
342+
throw new IllegalArgumentException("Wrong resize type [" + resizeRequest.getResizeType() + "] for indices clone request");
343+
}
344+
return resize(resizeRequest);
345+
}
346+
340347
private static Request resize(ResizeRequest resizeRequest) throws IOException {
341348
String endpoint = new RequestConverters.EndpointBuilder().addPathPart(resizeRequest.getSourceIndex())
342349
.addPathPartAsIs("_" + resizeRequest.getResizeType().name().toLowerCase(Locale.ROOT))

client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java

+24
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,30 @@ public void testSplit() throws IOException {
11281128
assertNotNull(aliasData);
11291129
}
11301130

1131+
@SuppressWarnings("unchecked")
1132+
public void testClone() throws IOException {
1133+
createIndex("source", Settings.builder().put("index.number_of_shards", 2).put("index.number_of_replicas", 0)
1134+
.put("index.number_of_routing_shards", 4).build());
1135+
updateIndexSettings("source", Settings.builder().put("index.blocks.write", true));
1136+
1137+
ResizeRequest resizeRequest = new ResizeRequest("target", "source");
1138+
resizeRequest.setResizeType(ResizeType.CLONE);
1139+
Settings targetSettings = Settings.builder().put("index.number_of_shards", 2).put("index.number_of_replicas", 0).build();
1140+
resizeRequest.setTargetIndex(new org.elasticsearch.action.admin.indices.create.CreateIndexRequest("target")
1141+
.settings(targetSettings)
1142+
.alias(new Alias("alias")));
1143+
ResizeResponse resizeResponse = execute(resizeRequest, highLevelClient().indices()::clone, highLevelClient().indices()::cloneAsync);
1144+
assertTrue(resizeResponse.isAcknowledged());
1145+
assertTrue(resizeResponse.isShardsAcknowledged());
1146+
Map<String, Object> getIndexResponse = getAsMap("target");
1147+
Map<String, Object> indexSettings = (Map<String, Object>)XContentMapValues.extractValue("target.settings.index", getIndexResponse);
1148+
assertNotNull(indexSettings);
1149+
assertEquals("2", indexSettings.get("number_of_shards"));
1150+
assertEquals("0", indexSettings.get("number_of_replicas"));
1151+
Map<String, Object> aliasData = (Map<String, Object>)XContentMapValues.extractValue("target.aliases.alias", getIndexResponse);
1152+
assertNotNull(aliasData);
1153+
}
1154+
11311155
public void testRollover() throws IOException {
11321156
highLevelClient().indices().create(new CreateIndexRequest("test").alias(new Alias("alias")), RequestOptions.DEFAULT);
11331157
RolloverRequest rolloverRequest = new RolloverRequest("alias", "test_new");

client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesRequestConvertersTests.java

+19-4
Original file line numberDiff line numberDiff line change
@@ -830,18 +830,33 @@ public void testSplit() throws IOException {
830830

831831
public void testSplitWrongResizeType() {
832832
ResizeRequest resizeRequest = new ResizeRequest("target", "source");
833-
resizeRequest.setResizeType(ResizeType.SHRINK);
833+
ResizeType wrongType = randomFrom(ResizeType.SHRINK, ResizeType.CLONE);
834+
resizeRequest.setResizeType(wrongType);
834835
IllegalArgumentException iae = LuceneTestCase.expectThrows(IllegalArgumentException.class, ()
835836
-> IndicesRequestConverters.split(resizeRequest));
836-
Assert.assertEquals("Wrong resize type [SHRINK] for indices split request", iae.getMessage());
837+
Assert.assertEquals("Wrong resize type [" + wrongType.name() + "] for indices split request", iae.getMessage());
838+
}
839+
840+
public void testClone() throws IOException {
841+
resizeTest(ResizeType.CLONE, IndicesRequestConverters::clone);
842+
}
843+
844+
public void testCloneWrongResizeType() {
845+
ResizeRequest resizeRequest = new ResizeRequest("target", "source");
846+
ResizeType wrongType = randomFrom(ResizeType.SHRINK, ResizeType.SPLIT);
847+
resizeRequest.setResizeType(wrongType);
848+
IllegalArgumentException iae = LuceneTestCase.expectThrows(IllegalArgumentException.class, ()
849+
-> IndicesRequestConverters.clone(resizeRequest));
850+
Assert.assertEquals("Wrong resize type [" + wrongType.name() + "] for indices clone request", iae.getMessage());
837851
}
838852

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

847862
public void testShrink() throws IOException {

client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java

+69
Original file line numberDiff line numberDiff line change
@@ -1808,6 +1808,75 @@ public void onFailure(Exception e) {
18081808
assertTrue(latch.await(30L, TimeUnit.SECONDS));
18091809
}
18101810

1811+
public void testCloneIndex() throws Exception {
1812+
RestHighLevelClient client = highLevelClient();
1813+
1814+
{
1815+
createIndex("source_index", Settings.builder().put("index.number_of_shards", 2).put("index.number_of_replicas", 0).build());
1816+
updateIndexSettings("source_index", Settings.builder().put("index.blocks.write", true));
1817+
}
1818+
1819+
// tag::clone-index-request
1820+
ResizeRequest request = new ResizeRequest("target_index","source_index"); // <1>
1821+
request.setResizeType(ResizeType.CLONE); // <2>
1822+
// end::clone-index-request
1823+
1824+
// tag::clone-index-request-timeout
1825+
request.timeout(TimeValue.timeValueMinutes(2)); // <1>
1826+
request.timeout("2m"); // <2>
1827+
// end::clone-index-request-timeout
1828+
// tag::clone-index-request-masterTimeout
1829+
request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); // <1>
1830+
request.masterNodeTimeout("1m"); // <2>
1831+
// end::clone-index-request-masterTimeout
1832+
// tag::clone-index-request-waitForActiveShards
1833+
request.setWaitForActiveShards(2); // <1>
1834+
request.setWaitForActiveShards(ActiveShardCount.DEFAULT); // <2>
1835+
// end::clone-index-request-waitForActiveShards
1836+
// tag::clone-index-request-settings
1837+
request.getTargetIndexRequest().settings(Settings.builder()
1838+
.put("index.number_of_shards", 2)); // <1>
1839+
// end::clone-index-request-settings
1840+
// tag::clone-index-request-aliases
1841+
request.getTargetIndexRequest().alias(new Alias("target_alias")); // <1>
1842+
// end::clone-index-request-aliases
1843+
1844+
// tag::clone-index-execute
1845+
ResizeResponse resizeResponse = client.indices().clone(request, RequestOptions.DEFAULT);
1846+
// end::clone-index-execute
1847+
1848+
// tag::clone-index-response
1849+
boolean acknowledged = resizeResponse.isAcknowledged(); // <1>
1850+
boolean shardsAcked = resizeResponse.isShardsAcknowledged(); // <2>
1851+
// end::clone-index-response
1852+
assertTrue(acknowledged);
1853+
assertTrue(shardsAcked);
1854+
1855+
// tag::clone-index-execute-listener
1856+
ActionListener<ResizeResponse> listener = new ActionListener<ResizeResponse>() {
1857+
@Override
1858+
public void onResponse(ResizeResponse resizeResponse) {
1859+
// <1>
1860+
}
1861+
1862+
@Override
1863+
public void onFailure(Exception e) {
1864+
// <2>
1865+
}
1866+
};
1867+
// end::clone-index-execute-listener
1868+
1869+
// Replace the empty listener by a blocking listener in test
1870+
final CountDownLatch latch = new CountDownLatch(1);
1871+
listener = new LatchedActionListener<>(listener, latch);
1872+
1873+
// tag::clone-index-execute-async
1874+
client.indices().cloneAsync(request, RequestOptions.DEFAULT,listener); // <1>
1875+
// end::clone-index-execute-async
1876+
1877+
assertTrue(latch.await(30L, TimeUnit.SECONDS));
1878+
}
1879+
18111880
public void testRolloverIndex() throws Exception {
18121881
RestHighLevelClient client = highLevelClient();
18131882

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
--
2+
:api: clone-index
3+
:request: ResizeRequest
4+
:response: ResizeResponse
5+
--
6+
7+
[id="{upid}-{api}"]
8+
=== Clone Index API
9+
10+
[id="{upid}-{api}-request"]
11+
==== Resize Request
12+
13+
The Clone Index API requires a +{request}+ instance.
14+
A +{request}+ requires two string arguments:
15+
16+
["source","java",subs="attributes,callouts,macros"]
17+
--------------------------------------------------
18+
include-tagged::{doc-tests-file}[{api}-request]
19+
--------------------------------------------------
20+
<1> The target index (first argument) to clone the source index (second argument) into
21+
<2> The resize type needs to be set to `CLONE`
22+
23+
==== Optional arguments
24+
The following arguments can optionally be provided:
25+
26+
["source","java",subs="attributes,callouts,macros"]
27+
--------------------------------------------------
28+
include-tagged::{doc-tests-file}[{api}-request-timeout]
29+
--------------------------------------------------
30+
<1> Timeout to wait for the all the nodes to acknowledge the index is opened
31+
as a `TimeValue`
32+
<2> Timeout to wait for the all the nodes to acknowledge the index is opened
33+
as a `String`
34+
35+
["source","java",subs="attributes,callouts,macros"]
36+
--------------------------------------------------
37+
include-tagged::{doc-tests-file}[{api}-request-masterTimeout]
38+
--------------------------------------------------
39+
<1> Timeout to connect to the master node as a `TimeValue`
40+
<2> Timeout to connect to the master node as a `String`
41+
42+
["source","java",subs="attributes,callouts,macros"]
43+
--------------------------------------------------
44+
include-tagged::{doc-tests-file}[{api}-request-waitForActiveShards]
45+
--------------------------------------------------
46+
<1> The number of active shard copies to wait for before the clone index API
47+
returns a response, as an `int`
48+
<2> The number of active shard copies to wait for before the clone index API
49+
returns a response, as an `ActiveShardCount`
50+
51+
["source","java",subs="attributes,callouts,macros"]
52+
--------------------------------------------------
53+
include-tagged::{doc-tests-file}[{api}-request-settings]
54+
--------------------------------------------------
55+
<1> The settings to apply to the target index, which optionally include the
56+
number of shards to create for it
57+
58+
["source","java",subs="attributes,callouts,macros"]
59+
--------------------------------------------------
60+
include-tagged::{doc-tests-file}[{api}-request-aliases]
61+
--------------------------------------------------
62+
<1> The aliases to associate the target index with
63+
64+
include::../execution.asciidoc[]
65+
66+
[id="{upid}-{api}-response"]
67+
==== Clone Index Response
68+
69+
The returned +{response}+ allows to retrieve information about the
70+
executed operation as follows:
71+
72+
["source","java",subs="attributes,callouts,macros"]
73+
--------------------------------------------------
74+
include-tagged::{doc-tests-file}[{api}-response]
75+
--------------------------------------------------
76+
<1> Indicates whether all of the nodes have acknowledged the request
77+
<2> Indicates whether the requisite number of shard copies were started for
78+
each shard in the index before timing out
79+
80+

docs/java-rest/high-level/supported-apis.asciidoc

+2
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ Index Management::
9999
* <<{upid}-close-index>>
100100
* <<{upid}-shrink-index>>
101101
* <<{upid}-split-index>>
102+
* <<{upid}-clone-index>>
102103
* <<{upid}-refresh>>
103104
* <<{upid}-flush>>
104105
* <<{upid}-flush-synced>>
@@ -133,6 +134,7 @@ include::indices/open_index.asciidoc[]
133134
include::indices/close_index.asciidoc[]
134135
include::indices/shrink_index.asciidoc[]
135136
include::indices/split_index.asciidoc[]
137+
include::indices/clone_index.asciidoc[]
136138
include::indices/refresh.asciidoc[]
137139
include::indices/flush.asciidoc[]
138140
include::indices/flush_synced.asciidoc[]

docs/reference/indices.asciidoc

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ index settings, aliases, mappings, and index templates.
1515
* <<indices-open-close>>
1616
* <<indices-shrink-index>>
1717
* <<indices-split-index>>
18+
* <<indices-clone-index>>
1819
* <<indices-rollover-index>>
1920
* <<freeze-index-api>>
2021
* <<unfreeze-index-api>>
@@ -72,6 +73,8 @@ include::indices/shrink-index.asciidoc[]
7273

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

76+
include::indices/clone-index.asciidoc[]
77+
7578
include::indices/rollover-index.asciidoc[]
7679

7780
include::indices/apis/freeze.asciidoc[]

0 commit comments

Comments
 (0)