Skip to content

Commit e8eabb3

Browse files
SCMOD-10180: Updated to ignore subfield issues (#16)
1 parent 1dc25ea commit e8eabb3

File tree

7 files changed

+569
-7
lines changed

7 files changed

+569
-7
lines changed

elastic-mapping-updater/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050
<groupId>com.google.guava</groupId>
5151
<artifactId>guava</artifactId>
5252
</dependency>
53+
<dependency>
54+
<groupId>org.apache.commons</groupId>
55+
<artifactId>commons-lang3</artifactId>
56+
</dependency>
5357
<dependency>
5458
<groupId>org.apache.httpcomponents</groupId>
5559
<artifactId>httpasyncclient</artifactId>

elastic-mapping-updater/src/main/java/com/github/cafdataprocessing/elastic/tools/ElasticMappingUpdater.java

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.io.IOException;
1919
import java.util.ArrayList;
20+
import java.util.Arrays;
2021
import java.util.Collections;
2122
import java.util.HashMap;
2223
import java.util.HashSet;
@@ -27,6 +28,7 @@
2728
import java.util.regex.Pattern;
2829
import java.util.stream.Collectors;
2930

31+
import org.apache.commons.lang3.StringUtils;
3032
import org.elasticsearch.client.indices.GetIndexResponse;
3133
import org.elasticsearch.client.indices.IndexTemplateMetaData;
3234
import org.elasticsearch.cluster.metadata.MappingMetaData;
@@ -53,6 +55,9 @@ public final class ElasticMappingUpdater
5355
private static final String MAPPING_DYNAMIC_TEMPLATES_KEY = "dynamic_templates";
5456
private static final String MAPPING_TYPE_KEY = "type";
5557

58+
private static final Set<String> UNSUPPORTED_PARAMS = Collections.unmodifiableSet(
59+
new HashSet<>(Arrays.asList("doc_values", "store")));
60+
5661
private final ObjectMapper objectMapper;
5762
private final ElasticRequestHandler elasticRequestHandler;
5863
private final boolean dryRun;
@@ -212,43 +217,83 @@ private void updateIndexesForTemplate(final IndexTemplateMetaData template)
212217
private static boolean isMappingChangeSafe(
213218
final Map<String, Object> templateMapping,
214219
final Map<String, Object> indexMapping,
215-
final Set<String> allowedFieldDifferences
220+
final Set<String> allowedFieldDifferences,
221+
final Set<String> unSupportedFieldDifferences
216222
)
217223
throws JsonProcessingException
218224
{
219225
final Map<String, Object> ftemplateMapping = FlatMapUtil.flatten(templateMapping);
220226
final Map<String, Object> findexMapping = FlatMapUtil.flatten(indexMapping);
221227
final MapDifference<String, Object> diff = Maps.difference(ftemplateMapping, findexMapping);
222228
final Map<String, ValueDifference<Object>> entriesDiffering = diff.entriesDiffering();
229+
boolean safeChangesOnly;
223230
if (entriesDiffering.isEmpty()) {
224-
return true;
231+
safeChangesOnly = true;
225232
} else {
226233
// Elasticsearch would throw IllegalArgumentException if any such
227234
// change is included in the index mapping updates
228235
entriesDiffering.forEach((key, value) -> {
229236
LOGGER.warn("Unsupported mapping change : {} - current: {} target: {}",
230237
key, value.rightValue(), value.leftValue());
231-
allowedFieldDifferences.remove(getFieldName(key));
238+
if(key.contains(MAPPING_PROPS_KEY))
239+
{
240+
// nested field
241+
unSupportedFieldDifferences.add(key);
242+
}
243+
else
244+
{
245+
allowedFieldDifferences.remove(getFieldName(key));
246+
}
232247
});
233-
return false;
248+
safeChangesOnly = false;
249+
}
250+
final Set<String> unsupportedParamChanges = new HashSet<>();
251+
final Map<String, Object> entriesOnlyInIndex = diff.entriesOnlyOnRight();
252+
// Field parameters that are currently set on a field in the index are now being removed
253+
entriesOnlyInIndex.entrySet().stream()
254+
.filter(e -> isUnsupportedParam(e.getKey()))
255+
.forEach(e -> {
256+
LOGGER.warn("Unsupported mapping change : field parameter being removed : {}:{}", e.getKey(), e.getValue());
257+
unsupportedParamChanges.add(e.getKey());
258+
}
259+
);
260+
if(!unsupportedParamChanges.isEmpty())
261+
{
262+
unSupportedFieldDifferences.addAll(unsupportedParamChanges);
263+
safeChangesOnly = false;
234264
}
265+
return safeChangesOnly;
266+
}
267+
268+
private static boolean isUnsupportedParam(final String fieldPath)
269+
{
270+
final String paramName = getParamName(fieldPath);
271+
return UNSUPPORTED_PARAMS.contains(paramName);
235272
}
236273

237274
private static String getFieldName(final String key)
238275
{
239276
return key.split(Pattern.quote("/"))[1];
240277
}
241278

279+
private static String getParamName(final String key)
280+
{
281+
final String[] path = key.split(Pattern.quote("/"));
282+
return path[path.length - 1];
283+
}
284+
242285
private Map<String, Object> getMappingChanges(final Map<String, Object> templateMapping, final Map<String, Object> indexMapping)
243286
throws JsonProcessingException
244287
{
245288
final Map<String, Object> mappingsChanges = new HashMap<>();
246289
final MapDifference<String, Object> diff = Maps.difference(templateMapping, indexMapping);
247290
final Map<String, ValueDifference<Object>> entriesDiffering = diff.entriesDiffering();
248291
final Set<String> allowedFieldDifferences = new HashSet<>(entriesDiffering.keySet());
292+
final Set<String> unSupportedFieldDifferences = new HashSet<>();
249293

250294
boolean unsupportedObjectChanges = false;
251295
if (!entriesDiffering.isEmpty()) {
296+
// Template has mapping changes to existing properties
252297
LOGGER.info("--Differences between template and index mapping--");
253298
entriesDiffering.forEach((key, value) -> LOGGER.info(" {} - current: {} target: {}",
254299
key, value.rightValue(), value.leftValue()));
@@ -286,7 +331,8 @@ private Map<String, Object> getMappingChanges(final Map<String, Object> template
286331
}
287332
}
288333

289-
if (!isMappingChangeSafe(templateMapping, indexMapping, allowedFieldDifferences) || unsupportedObjectChanges) {
334+
if (!isMappingChangeSafe(templateMapping, indexMapping, allowedFieldDifferences, unSupportedFieldDifferences)
335+
|| unsupportedObjectChanges) {
290336
LOGGER.warn("Unsupported mapping changes will not be applied to the index.");
291337
}
292338

@@ -297,6 +343,15 @@ private Map<String, Object> getMappingChanges(final Map<String, Object> template
297343
mappingsChanges.put(field, ((ValueDifference<?>) entriesDiffering.get(field)).leftValue());
298344
}
299345

346+
// Remove any unsupportedMappings
347+
LOGGER.info("{}", unSupportedFieldDifferences.isEmpty()
348+
? "No unsupported field changes."
349+
: "Unsupported field changes that will not be included in the update: " + unSupportedFieldDifferences);
350+
for (final String field : unSupportedFieldDifferences) {
351+
removeUnsupportedFieldChange(mappingsChanges, field);
352+
}
353+
354+
// Add new properties defined in the template
300355
final Map<String, Object> entriesOnlyInTemplate = diff.entriesOnlyOnLeft();
301356
final Set<String> newProperties = entriesOnlyInTemplate.keySet();
302357
LOGGER.info("{}", newProperties.isEmpty()
@@ -314,4 +369,31 @@ private boolean hasDynamicTemplateChanged(List<Object> dynamicTemplatesInTemplat
314369
return dynamicTemplatesInTemplate.retainAll(dynamicTemplatesInIndex);
315370
}
316371

372+
@SuppressWarnings("unchecked")
373+
private void removeUnsupportedFieldChange(final Map<String, Object> mappingsChanges, final String fieldPath) {
374+
final List<String> path = Arrays.asList(StringUtils.split(fieldPath.trim(), "/"));
375+
final int size = path.size();
376+
int index = 0;
377+
if(size == 2)
378+
{
379+
// for a field path like, /LANGUAGE_CODES/properties/CODE/type, the 'fieldName' to be removed here is 'CODE'
380+
// remove property with unsupported mapping change
381+
final String fieldName = path.get(0);
382+
mappingsChanges.remove(fieldName);
383+
}
384+
else
385+
{
386+
while (index != size - 2) {
387+
final int i = index++;
388+
final String currentFieldName = path.get(i);
389+
final Object field = mappingsChanges.get(path.get(i));
390+
if (field instanceof Map<?, ?>) {
391+
final Map<String, Object> currentField = (Map<String, Object>) field;
392+
final String subPath = fieldPath.substring(fieldPath.indexOf(currentFieldName) + currentFieldName.length());
393+
removeUnsupportedFieldChange(currentField, subPath);
394+
}
395+
}
396+
}
397+
}
398+
317399
}

elastic-mapping-updater/src/test/java/com/github/cafdataprocessing/elastic/tools/test/ElasticMappingUpdaterIT.java

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.github.cafdataprocessing.elastic.tools.test;
1717

18+
import static org.junit.Assert.assertFalse;
1819
import static org.junit.Assert.assertNotNull;
1920
import static org.junit.Assert.assertTrue;
2021
import static org.junit.Assert.fail;
@@ -439,14 +440,31 @@ public void testUpdateIndexesOfUnSupportedChangesInTemplate() throws IOException
439440
LOGGER.info("idPropMapping {} ", idPropMapping);
440441
final Object idPropValue = idPropMapping.get("ignore_malformed");
441442
// Verify property mapping parameter was not removed (although mapping update was to remove the ignore_malformed parameter)
442-
assertNotNull("testUpdateUnsupportedChanges", idPropValue);
443+
assertNotNull("testUpdateIndexesOfUnSupportedChangesInTemplate", idPropValue);
443444

444445
// Verify index mapping of unsupported field changes has not changed
445446
@SuppressWarnings("unchecked")
446447
final Map<String, Object> langPropMapping = (Map<String, Object>) props.get("LANGUAGE_CODES");
447448
final String propValue = (String) langPropMapping.get("type");
448449
// Verify property mapping value is same as before
449-
assertTrue("testUpdateUnsupportedChanges", propValue.equals("nested"));
450+
assertTrue("testUpdateIndexesOfUnSupportedChangesInTemplate", propValue.equals("nested"));
451+
452+
// Verify index mapping of unsupported field changes has been updated with allowed changes
453+
@SuppressWarnings("unchecked")
454+
final Map<String, Object> targetRefPropMapping = (Map<String, Object>) props.get("TARGET_REFERENCES");
455+
@SuppressWarnings("unchecked")
456+
final Map<String, Object> targetRefProps = (Map<String, Object>) targetRefPropMapping.get("properties");
457+
// Verify new property is added
458+
@SuppressWarnings("unchecked")
459+
final Map<String, Object> tRefMapping = (Map<String, Object>) targetRefProps.get("TARGET_REFERENCE");
460+
assertNotNull("testUpdateIndexesOfUnSupportedChangesInTemplate", tRefMapping);
461+
// Verify unsupported change to nested property is not applied
462+
final Boolean propDocValuesValue = (Boolean) tRefMapping.get("doc_values");
463+
assertFalse("testUpdateIndexesOfUnSupportedChangesInTemplate", propDocValuesValue);
464+
// Verify new nested property is added
465+
@SuppressWarnings("unchecked")
466+
final Map<String, Object> destMapping = (Map<String, Object>) targetRefProps.get("DESTINATION_ID");
467+
assertNotNull("testUpdateIndexesOfUnSupportedChangesInTemplate", destMapping);
450468

451469
// Index more data
452470
request = new IndexRequest(indexName);
@@ -475,6 +493,95 @@ public void testUpdateIndexesOfUnSupportedChangesInTemplate() throws IOException
475493
verifyIndexData(indexName, QueryBuilders.matchAllQuery(), 2);
476494
}
477495

496+
@Test
497+
public void testUpdateIndexesWithNestedFieldChanges() throws IOException, GetIndexException, InterruptedException
498+
{
499+
LOGGER.info("Running test 'testUpdateIndexesWithNestedFieldChanges'...");
500+
final String templateName = "sample-template";
501+
final String origTemplateSourceFile = "/template10.json";
502+
// 'store' param removed from nested property LANGUAGE_CODES/CODE
503+
// nested property type='nested' is removed
504+
final String updatedTemplateSourceFile = "/template11.json";
505+
final String indexName = "jan_blue-000001";
506+
507+
final String origTemplateSource = readFile(origTemplateSourceFile);
508+
LOGGER.info("testUpdateIndexesWithNestedFieldChanges - Creating initial template {}", templateName);
509+
510+
// Create a template
511+
final PutIndexTemplateRequest trequest = new PutIndexTemplateRequest(templateName);
512+
trequest.source(origTemplateSource, XContentType.JSON);
513+
final AcknowledgedResponse putTemplateResponse = client.indices().putTemplate(trequest, RequestOptions.DEFAULT);
514+
if (!putTemplateResponse.isAcknowledged()) {
515+
fail();
516+
}
517+
LOGGER.info("testUpdateIndexesWithNestedFieldChanges - Creating index matching template {}", templateName);
518+
// Create an index with some data
519+
IndexRequest request = new IndexRequest(indexName);
520+
request.id("1");
521+
request.routing("1");
522+
String jsonString = "{" + "'TITLE':'doc1'," + "'DATE_PROCESSED\":'2020-02-11'," + "'CONTENT_PRIMARY':'just a test',"
523+
+ "'IS_HEAD_OF_FAMILY':true," + "'PERSON':{ 'NAME':'person1' }" + "}";
524+
jsonString = jsonString.replaceAll("'", "\"");
525+
request.source(jsonString, XContentType.JSON);
526+
request.setRefreshPolicy(RefreshPolicy.IMMEDIATE);
527+
final boolean needsRetries = indexDocumentWithRetry(request);
528+
if (needsRetries) {
529+
// Indexing has failed after multiple retries
530+
fail();
531+
}
532+
533+
verifyIndexData(indexName, QueryBuilders.matchAllQuery(), 1);
534+
535+
LOGGER.info("testUpdateIndexesWithNestedFieldChanges - Updating template {}", templateName);
536+
final String updatedTemplateSource = readFile(updatedTemplateSourceFile);
537+
// Create a template
538+
final PutIndexTemplateRequest utrequest = new PutIndexTemplateRequest(templateName);
539+
utrequest.source(updatedTemplateSource, XContentType.JSON);
540+
final AcknowledgedResponse updateTemplateResponse = client.indices().putTemplate(utrequest, RequestOptions.DEFAULT);
541+
if (!updateTemplateResponse.isAcknowledged()) {
542+
fail();
543+
}
544+
545+
LOGGER.info("testUpdateIndexesWithNestedFieldChanges - Updating indexes matching template {}", templateName);
546+
updateIndex("testUpdateIndexesWithNestedFieldChanges", templateName);
547+
548+
// Verify index mapping has new properties
549+
final Map<String, Object> indexTypeMappings = getIndexMapping(indexName);
550+
@SuppressWarnings("unchecked")
551+
final Map<String, Object> props = (Map<String, Object>) indexTypeMappings.get("properties");
552+
@SuppressWarnings("unchecked")
553+
final Map<String, Object> propMapping = (Map<String, Object>) props.get("DATE_DISPOSED");
554+
assertNotNull("testUpdateIndexesWithNestedFieldChanges", propMapping);
555+
@SuppressWarnings("unchecked")
556+
final Map<String, Object> entPropMapping = (Map<String, Object>) props.get("ENTITIES");
557+
LOGGER.info("entitiesPropMapping {} ", entPropMapping);
558+
559+
// Index more data
560+
request = new IndexRequest(indexName);
561+
request.id("2");
562+
request.routing("1");
563+
jsonString = "{"
564+
+ "'TITLE':'doc2',"
565+
+ "'DATE_PROCESSED':'2020-02-11',"
566+
+ "'CONTENT_PRIMARY':'just a test',"
567+
+ "'IS_HEAD_OF_FAMILY':true,"
568+
+ "'PERSON':{ 'NAME':'person2', 'AGE':5 },"
569+
+ "'HOLD_DETAILS': {'FIRST_HELD_DATE':'2020-02-11', 'HOLD_HISTORY': '2020-02-11', 'HOLD_ID': '12'}"
570+
+ "}";
571+
jsonString = jsonString.replaceAll("'", "\"");
572+
request.source(jsonString, XContentType.JSON);
573+
request.setRefreshPolicy(RefreshPolicy.IMMEDIATE);
574+
575+
try {
576+
final IndexResponse response = client.index(request, RequestOptions.DEFAULT);
577+
assertTrue(response.status() == RestStatus.CREATED);
578+
} catch (final ElasticsearchException e) {
579+
fail();
580+
}
581+
582+
verifyIndexData(indexName, QueryBuilders.matchAllQuery(), 2);
583+
}
584+
478585
private void updateIndex(final String testName, final String templateName)
479586
{
480587
LOGGER.info("{}: {}", testName, templateName);

0 commit comments

Comments
 (0)