Skip to content

Commit 6a252dd

Browse files
opensearch-trigger-bot[bot]github-actions[bot]andrross
authored
Enforce 512 byte document ID limit in bulk updates (#8039) (#8081)
* Enforce doc id limit on UpdateRequests as well * PR comments * Address comments 2 * Move changelog entry --------- (cherry picked from commit 60afeb7) Signed-off-by: Ankit Kala <[email protected]> Signed-off-by: Andrew Ross <[email protected]> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Andrew Ross <[email protected]>
1 parent 25b6b4c commit 6a252dd

File tree

6 files changed

+106
-7
lines changed

6 files changed

+106
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Remove `COMPRESSOR` variable from `CompressorFactory` and use `DEFLATE_COMPRESSO
5353

5454
### Fixed
5555
- Fixing error: adding a new/forgotten parameter to the configuration for checking the config on startup in plugins/repository-s3 #7924
56+
- Enforce 512 byte document ID limit in bulk updates ([#8039](https://github.com/opensearch-project/OpenSearch/pull/8039))
5657

5758
### Security
5859

server/src/internalClusterTest/java/org/opensearch/action/bulk/BulkIntegrationIT.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.opensearch.action.ingest.PutPipelineRequest;
4444
import org.opensearch.action.support.master.AcknowledgedResponse;
4545
import org.opensearch.action.support.replication.ReplicationRequest;
46+
import org.opensearch.action.update.UpdateRequest;
4647
import org.opensearch.cluster.metadata.IndexMetadata;
4748
import org.opensearch.common.bytes.BytesReference;
4849
import org.opensearch.core.xcontent.XContentBuilder;
@@ -218,4 +219,38 @@ public void testDeleteIndexWhileIndexing() throws Exception {
218219
assertFalse(thread.isAlive());
219220
}
220221

222+
public void testDocIdTooLong() {
223+
String index = "testing";
224+
createIndex(index);
225+
String validId = String.join("", Collections.nCopies(512, "a"));
226+
String invalidId = String.join("", Collections.nCopies(513, "a"));
227+
228+
// Index Request
229+
IndexRequest indexRequest = new IndexRequest(index).source(Collections.singletonMap("foo", "baz"));
230+
// Valid id shouldn't throw any exception
231+
assertFalse(client().prepareBulk().add(indexRequest.id(validId)).get().hasFailures());
232+
// Invalid id should throw the ActionRequestValidationException
233+
validateDocIdLimit(() -> client().prepareBulk().add(indexRequest.id(invalidId)).get());
234+
235+
// Update Request
236+
UpdateRequest updateRequest = new UpdateRequest(index, validId).doc("reason", "no source");
237+
// Valid id shouldn't throw any exception
238+
assertFalse(client().prepareBulk().add(updateRequest).get().hasFailures());
239+
// Invalid id should throw the ActionRequestValidationException
240+
validateDocIdLimit(() -> client().prepareBulk().add(updateRequest.id(invalidId)).get());
241+
}
242+
243+
private void validateDocIdLimit(Runnable runner) {
244+
try {
245+
runner.run();
246+
fail("Request validation for docId didn't fail");
247+
} catch (ActionRequestValidationException e) {
248+
assertEquals(
249+
1,
250+
e.validationErrors().stream().filter(msg -> msg.contains("is too long, must be no longer than 512 bytes but was")).count()
251+
);
252+
} catch (Exception e) {
253+
fail("Request validation for docId failed with different exception: " + e);
254+
}
255+
}
221256
}

server/src/main/java/org/opensearch/action/DocWriteRequest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
package org.opensearch.action;
3333

3434
import org.apache.lucene.util.Accountable;
35+
import org.apache.lucene.util.UnicodeUtil;
3536
import org.opensearch.action.delete.DeleteRequest;
3637
import org.opensearch.action.index.IndexRequest;
3738
import org.opensearch.action.support.IndicesOptions;
@@ -249,6 +250,25 @@ static DocWriteRequest<?> readDocumentRequest(@Nullable ShardId shardId, StreamI
249250
return docWriteRequest;
250251
}
251252

253+
/**
254+
* Validates whether the doc id length is under the limit.
255+
* @param id DocId to verify
256+
* @param validationException containing all the validation errors.
257+
* @return validationException
258+
*/
259+
static ActionRequestValidationException validateDocIdLength(String id, ActionRequestValidationException validationException) {
260+
if (id != null) {
261+
int docIdLength = UnicodeUtil.calcUTF16toUTF8Length(id, 0, id.length());
262+
if (docIdLength > 512) {
263+
return addValidationError(
264+
"id [" + id + "] is too long, must be no longer than 512 bytes but was: " + docIdLength,
265+
validationException
266+
);
267+
}
268+
}
269+
return validationException;
270+
}
271+
252272
/** write a document write (index/delete/update) request*/
253273
static void writeDocumentRequest(StreamOutput out, DocWriteRequest<?> request) throws IOException {
254274
if (request instanceof IndexRequest) {

server/src/main/java/org/opensearch/action/index/IndexRequest.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@
6464
import org.opensearch.index.shard.ShardId;
6565

6666
import java.io.IOException;
67-
import java.nio.charset.StandardCharsets;
6867
import java.util.Locale;
6968
import java.util.Map;
7069
import java.util.Objects;
@@ -238,12 +237,7 @@ public ActionRequestValidationException validate() {
238237

239238
validationException = DocWriteRequest.validateSeqNoBasedCASParams(this, validationException);
240239

241-
if (id != null && id.getBytes(StandardCharsets.UTF_8).length > 512) {
242-
validationException = addValidationError(
243-
"id [" + id + "] is too long, must be no longer than 512 bytes but was: " + id.getBytes(StandardCharsets.UTF_8).length,
244-
validationException
245-
);
246-
}
240+
validationException = DocWriteRequest.validateDocIdLength(id, validationException);
247241

248242
if (pipeline != null && pipeline.isEmpty()) {
249243
validationException = addValidationError("pipeline cannot be an empty string", validationException);

server/src/main/java/org/opensearch/action/update/UpdateRequest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ public ActionRequestValidationException validate() {
254254
if (doc == null && docAsUpsert) {
255255
validationException = addValidationError("doc must be specified if doc_as_upsert is enabled", validationException);
256256
}
257+
258+
validationException = DocWriteRequest.validateDocIdLength(id, validationException);
259+
257260
return validationException;
258261
}
259262

server/src/test/java/org/opensearch/action/bulk/BulkRequestTests.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import java.io.IOException;
5454
import java.nio.charset.StandardCharsets;
5555
import java.util.ArrayList;
56+
import java.util.Collections;
5657
import java.util.HashMap;
5758
import java.util.List;
5859
import java.util.Map;
@@ -272,6 +273,51 @@ public void testBulkRequestWithRefresh() throws Exception {
272273
);
273274
}
274275

276+
public void testBulkRequestInvalidDocIDDuringCreate() {
277+
String validDocID = String.join("", Collections.nCopies(512, "a"));
278+
String invalidDocID = String.join("", Collections.nCopies(513, "a"));
279+
280+
// doc id length under limit
281+
IndexRequest indexRequest = new IndexRequest("index").id(validDocID).source(Requests.INDEX_CONTENT_TYPE, "field", "value");
282+
BulkRequest bulkRequest = new BulkRequest();
283+
bulkRequest.add(indexRequest);
284+
assertNull(bulkRequest.validate());
285+
286+
// doc id length over limit
287+
indexRequest.id(invalidDocID);
288+
ActionRequestValidationException validate = bulkRequest.validate();
289+
assertThat(validate, notNullValue());
290+
assertEquals(
291+
1,
292+
validate.validationErrors()
293+
.stream()
294+
.filter(msg -> msg.contains("is too long, must be no longer than 512 bytes but was: "))
295+
.count()
296+
);
297+
}
298+
299+
public void testBulkRequestInvalidDocIDDuringUpdate() {
300+
String validDocID = String.join("", Collections.nCopies(512, "a"));
301+
String invalidDocID = String.join("", Collections.nCopies(513, "a"));
302+
// doc id length under limit
303+
UpdateRequest updateRequest = new UpdateRequest("index", validDocID).doc("reason", "no source");
304+
BulkRequest bulkRequest = new BulkRequest();
305+
bulkRequest.add(updateRequest);
306+
assertNull(bulkRequest.validate());
307+
308+
// doc id length over limit
309+
updateRequest.id(invalidDocID);
310+
ActionRequestValidationException validate = bulkRequest.validate();
311+
assertThat(validate, notNullValue());
312+
assertEquals(
313+
1,
314+
validate.validationErrors()
315+
.stream()
316+
.filter(msg -> msg.contains("is too long, must be no longer than 512 bytes but was: "))
317+
.count()
318+
);
319+
}
320+
275321
// issue 15120
276322
public void testBulkNoSource() throws Exception {
277323
BulkRequest bulkRequest = new BulkRequest();

0 commit comments

Comments
 (0)