Skip to content
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

Disallow "enabled" attribute change for types in mapping update (#33566) #33933

Merged
merged 13 commits into from
Oct 1, 2018
Merged

Disallow "enabled" attribute change for types in mapping update (#33566) #33933

merged 13 commits into from
Oct 1, 2018

Conversation

cbismuth
Copy link
Contributor

@cbismuth cbismuth commented Sep 21, 2018

This commit adds a check for enabled attribute change for types when a RestPutMappingAction is received. A MappingException is thrown when such a change is detected.

Change are prevented in both ways: false to true and true to false.

Here is a sample cURL output:

{
  "error": {
    "root_cause": [
      {
        "type": "mapper_exception",
        "reason": "Can't update attribute for type [type1.foo.enabled] in index mapping"
      }
    ],
    "type": "mapper_exception",
    "reason": "Can't update attribute for type [type1.foo.enabled] in index mapping"
  },
  "status": 500
}

Tests are not part of this commit, they will be developed after design validation.

This commit adds a check for "enabled" attribute change for types when
a RestPutMappingAction is received. A MappingException is thrown when
such a change is detected.

Change are prevented in both ways: "false -> true" and "true -> false".

Tests are not part of this commit, they will be developed after design
validation.
@cbuescher cbuescher added the :Search Foundations/Mapping Index mappings, including merging and defining field types label Sep 21, 2018
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-search-aggs

Copy link
Member

@cbuescher cbuescher left a comment

Choose a reason for hiding this comment

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

HI @cbismuth thanks for looking into #33566. I left a few very small comments and a questions. In general I think adding a few tests now would help understand which cases this fix covers, but generally I think the approach looks good.

@@ -33,11 +33,15 @@ public MapperException(String message) {
super(message);
}

public MapperException(String msg, Object... args) {
super(msg, args);
}
Copy link
Member

Choose a reason for hiding this comment

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

nit: not sure we need another constructor here, can you just insert the args into the message String in the call?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure.
Is LoggerMessageFormat#format preferred over String#format or maybe just string concatenation?

@@ -458,6 +458,8 @@ protected void doMerge(final ObjectMapper mergeWith) {

for (Mapper mergeWithMapper : mergeWith) {
Mapper mergeIntoMapper = mappers.get(mergeWithMapper.simpleName());
throwExceptionIfEnabledAttributeIsUpdatedForType(mergeWith, mergeWithMapper, mergeIntoMapper);
Copy link
Member

Choose a reason for hiding this comment

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

nit: ImNotABigFanOfOverlyLongMethodNamesButWhatTheHeckIfYouReallyLikeIt ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll shorten it, I'm not a big fan neither.
As I wasn't sure of the suggested design I've chosen (very) (very) explicit name to make myself understood.

final ObjectMapper mergeWithObjectMapper = (ObjectMapper) mergeWithMapper;

if (mergeIntoObjectMapper.isEnabled() != mergeWithObjectMapper.isEnabled()) {
if (mergeIntoObjectMapper.nestedTypeFilter() instanceof TermQuery) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not quite sure I understand what the enable setting in mappings that are merged have to do with nested queries, especially with TermQuery. Maybe I'm missing sth., could you explain?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I dug into this with a debugger and the _type attribute happened to be wrapped into a TermQuery.

I've been a (tiny) bit paranoiac as I wanted to be sure to not make other things break.

I'll have another debugging session to see how things work when updating mapping with "_source": { "enabled": false }. I'll let you know.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, it looks like updating the _source attribute in a mapping is not allowed.

command

curl -XPUT "${ES_CLUSTER}/index1/_mapping/type1" -H "Content-Type: application/json" --data '{
  "properties": {
    "foo": {
      "source": {
        "enabled": false
      },
      "properties": {
        "bar": {
          "type": "text"
        }
      }
    }
  }
}'

output

{
  "error": {
    "root_cause": [
      {
        "type": "mapper_parsing_exception",
        "reason": "Mapping definition for [foo] has unsupported parameters:  [source : {enabled=false}]"
      }
    ],
    "type": "mapper_parsing_exception",
    "reason": "Mapping definition for [foo] has unsupported parameters:  [source : {enabled=false}]"
  },
  "status": 400
}

I'll now have a look if removing this conditional block doesn't conflict with other enabled-like metadata attributes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No gradlew check -p server test failure after removing paranoiac conditional block 👍

@cbismuth
Copy link
Contributor Author

Thank you @cbuescher 👍 I'll update this PR with your comments and add test cases.

@cbismuth
Copy link
Contributor Author

Hi @cbuescher, I've added test cases to ensure added behavior is covered.

Copy link
Member

@cbuescher cbuescher left a comment

Choose a reason for hiding this comment

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

Hi @cbismuth, thanks a lot for the update, the test looks good and I left only a few very minor comments.


if (mergeIntoObjectMapper.isEnabled() != mergeWithObjectMapper.isEnabled()) {
final String path = mergeWith.fullPath() + "." + mergeWithObjectMapper.simpleName() + ".enabled";

Copy link
Member

Choose a reason for hiding this comment

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

nit: maybe delete this line

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, these two lines are related, no need to add a blank line, I'll remove it.

private static Explicit<Boolean> dateDetection = new Explicit<>(false, false);
private static Explicit<Boolean> numericDetection = new Explicit<>(false, false);
private static Explicit<FormatDateTimeFormatter[]> dynamicDateTimeFormatters = new Explicit<>(new FormatDateTimeFormatter[0], false);
private static Explicit<DynamicTemplate[]> dynamicTemplates = new Explicit<>(new DynamicTemplate[0], false);
Copy link
Member

Choose a reason for hiding this comment

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

I was a bit suprised by these until I saw them being used in createRootObjectMapper(), maybe you could just instantiate them there locally, even if that means a few more instances. I think it makes the test more readable although this way is certainly "better" in terms of object creation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, you're right. It would be a better balance between performances and readability. I'll do it, thank you.

@cbismuth
Copy link
Contributor Author

Thank you @cbuescher for your review. I've added comments to your comments and I'll update my PR very soon.
Even with minor comments, it's always interesting to see how pieces of code are read, thank you for your time.

@cbismuth
Copy link
Contributor Author

PR updated with your comments, thank you @cbuescher.

Copy link
Member

@cbuescher cbuescher left a comment

Choose a reason for hiding this comment

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

I left two more small comment, but generally this looks almost ready.


private static TextFieldMapper createTextFieldMapper(String name) {
final TextFieldType fieldType = new TextFieldType();
final Settings indexSettings = Settings.builder().put(SETTING_VERSION_CREATED, "1").build();
Copy link
Member

Choose a reason for hiding this comment

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

nit: I guess it doesn't matter here too much, but normally one of the version constants from org.elasticsearch.Version is used in these cases, usually Version.CURRENT

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's better as it's closer to production code.

dynamicDateTimeFormatters, dynamicTemplates,
dateDetection, numericDetection,
Settings.EMPTY
);
Copy link
Member

Choose a reason for hiding this comment

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

nit: I'm not too familiar with mapper creation (especially in tests) myself, but I saw that the RootObjectMapper.Builder already takes care of a lot of the defaults, so maybe this can be simplified to something like:

        final Settings indexSettings = Settings.builder().put(SETTING_VERSION_CREATED, Version.CURRENT).build();
        BuilderContext context = new BuilderContext(indexSettings, new ContentPath());
        RootObjectMapper rootObjectMapper = new RootObjectMapper.Builder(fieldName).enabled(enabled).build(context);
        mappers.values().stream().forEach(rootObjectMapper::putMapper);
        return rootObjectMapper;

Or maybe even the dummy settings could be a test-wide constant.

I don't think this is necesarry but removes a bit of boilerplate code (which I admit testing the mappers seems to require quite a bit).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it's far more better, builders are far more expressive than lengthy constructors.

@cbismuth
Copy link
Contributor Author

Thank you @cbuescher for your attention to details, I know it can be very time consuming.

PR is up-to-date with your comments 👍

Copy link
Member

@cbuescher cbuescher left a comment

Choose a reason for hiding this comment

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

@cbismuth thanks, this looks good to me. I will kick of final CI test runs now, hope they pass in one go 🤞
@elasticmachine test this please

@cbuescher
Copy link
Member

@elasticmachine test this please

@cbismuth
Copy link
Contributor Author

Great! Thanks @cbuescher!

@cbismuth
Copy link
Contributor Author

runbld>>> Test output logs contained: Errors: 3 Failures: 72 Tests: 17654 Skipped: 297

That's a lot of failures! I've downloaded the plain text output log and I'll replay failed tests on my laptop on Monday morning.

Anyway, number of them seem to have the same stash dump, here is a sample below.

  1> [2018-09-28T10:53:55,638][INFO ][o.e.b.MixedClusterClientYamlTestSuiteIT] [test] Stash dump on test failure [{
  1>   "stash" : {
  1>     "body" : {
  1>       "_shards" : {
  1>         "total" : 40,
  1>         "successful" : 32,
  1>         "failed" : 2,
  1>         "failures" : [
  1>           {
  1>             "shard" : 2,
  1>             "index" : "test-empty",
  1>             "status" : "INTERNAL_SERVER_ERROR",
  1>             "reason" : {
  1>               "type" : "illegal_state_exception",
  1>               "reason" : "Message not fully read (request) for requestId [3973], action [indices:admin/refresh[s][r]], available [0]; resetting"
  1>             }
  1>           },
  1>           {
  1>             "shard" : 1,
  1>             "index" : "test-unmapped",
  1>             "status" : "INTERNAL_SERVER_ERROR",
  1>             "reason" : {
  1>               "type" : "illegal_state_exception",
  1>               "reason" : "Message not fully read (request) for requestId [3976], action [indices:admin/refresh[s][r]], available [0]; resetting"
  1>             }
  1>           }
  1>         ]
  1>       }
  1>     }
  1>   }
  1> }]

@jasontedor
Copy link
Member

@cbismuth That's from BWC issues that can happen when another change is merged to 6.x. Those should go away if you merge master into your branch.

@cbuescher cbuescher merged commit 2923fb5 into elastic:master Oct 1, 2018
@cbismuth
Copy link
Contributor Author

cbismuth commented Oct 1, 2018

Thank you @cbuescher 👍

Would you like me to test it on a 6.5 branch and let you know?

@cbuescher
Copy link
Member

cbuescher commented Oct 1, 2018

@cbismuth thanks, I think it should be a straigtforward backport, the question is more if this is breaking users applications even though the feature never worked as intended. But throwing an error is different than silently ignoring. It could be considered a bugfix though, in that case if would justify the backport. But I'll see what others think.

@cbismuth
Copy link
Contributor Author

cbismuth commented Oct 1, 2018

It is indeed a good question 😉

@cbuescher
Copy link
Member

@cbismuth I got some feedback that it would probably better not to backport the new strict checking since it would be considered breaking. However, it would be nice to change the PR slightly so that instead of throwing and error, a deprecation warning is emitted warning users that their request was silently ignored and we will throw an error here in the next major version. I will open a separate issue for this, it would be great if you would like to adapt this PR slightly to address it, but its also no problem if you don't like to do that. There a only a few thing that need changing for this I think, I can give you directions if you like to work on this as well.

@cbuescher
Copy link
Member

cbuescher commented Oct 2, 2018

@cbismuth I have to correct myself, the general opinion shifted to backporting this change to 6.5 and add it to the breaking changes log. I will do this with the backport.

cbuescher pushed a commit that referenced this pull request Oct 2, 2018
This commit adds a check for "enabled" attribute change for types when
a RestPutMappingAction is received. A MappingException is thrown when
such a change is detected.  Change are prevented in both ways: "false -> true"
and "true -> false".

Closes #33566
@cbismuth
Copy link
Contributor Author

cbismuth commented Oct 2, 2018

That's fine, thank you @cbuescher.

@cbismuth cbismuth deleted the 33566_disallow_type_enabled_mapping_update branch October 25, 2018 17:39
kcm pushed a commit that referenced this pull request Oct 30, 2018
This commit adds a check for "enabled" attribute change for types when
a RestPutMappingAction is received. A MappingException is thrown when
such a change is detected.  Change are prevented in both ways: "false -> true" 
and "true -> false".

Closes #33566
henningandersen added a commit to henningandersen/elasticsearch that referenced this pull request Jun 27, 2019
Removed the invalid tip that enabled can be updated for existing fields
and clarified instead that it cannot.

Related to elastic#33566 and elastic#33933
henningandersen added a commit that referenced this pull request Jun 28, 2019
Removed the invalid tip that enabled can be updated for existing fields
and clarified instead that it cannot.

Related to #33566 and #33933
henningandersen added a commit that referenced this pull request Jun 28, 2019
Removed the invalid tip that enabled can be updated for existing fields
and clarified instead that it cannot.

Related to #33566 and #33933
henningandersen added a commit that referenced this pull request Jun 28, 2019
Removed the invalid tip that enabled can be updated for existing fields
and clarified instead that it cannot.

Related to #33566 and #33933
henningandersen added a commit that referenced this pull request Jun 28, 2019
Removed the invalid tip that enabled can be updated for existing fields
and clarified instead that it cannot.

Related to #33566 and #33933
jtibshirani added a commit that referenced this pull request Apr 2, 2020
In #33933 we disallowed changing the `enabled` parameter in object mappings.
However, the fix didn't cover the root object mapper. This PR adjusts the change
to also include the root mapper and clarifies the error message.
jtibshirani added a commit that referenced this pull request Apr 2, 2020
In #33933 we disallowed changing the `enabled` parameter in object mappings.
However, the fix didn't cover the root object mapper. This PR adjusts the change
to also include the root mapper and clarifies the error message.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
>breaking >enhancement :Search Foundations/Mapping Index mappings, including merging and defining field types v6.5.0 v7.0.0-beta1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants