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

Remove PROTO-serialization from IndexMetaData.Custom #32749

Merged

Conversation

imotov
Copy link
Contributor

@imotov imotov commented Aug 9, 2018

Switches IndexMetaData.Custom to the standard named serialization.

Supersedes #32683

@jasontedor this is a version of #32683 without any breaking changes in terms of functionality and user interface. It will require small changes in existing plugins that use IndexMetaData.Custom, but otherwise, I think there is total parity with existing implementation minus PROTO-serialization.

@tlrx could you take a look at my (mis)use of SPI and XContent parsers in CreateIndexRequest and PutIndexTemplateRequest? Did I miss anything there?

Switches IndexMetaData.Custom to standard named serialization.

Supersedes elastic#32683
@imotov imotov added :Data Management/Indices APIs APIs to create and manage indices and templates v7.0.0 >refactoring v6.5.0 labels Aug 9, 2018
@imotov imotov requested review from dakrone and tlrx August 9, 2018 15:29
@elasticmachine
Copy link
Collaborator

Pinging @elastic/es-core-infra

@imotov
Copy link
Contributor Author

imotov commented Aug 13, 2018

retest this please

if (entry.getValue() instanceof Map) {
if (customsRegistry == null) {
// Custom registry wasn't specified - we are in client mode let's try loading it from SPI
customsRegistry = new NamedXContentRegistry(getProvidedNamedXContents());
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 sure to understand what you want to achieve here (though I understand the proto based serialization removal).

I don't think we want a request object to create a NamedXContentRegistry with only some provided names xcontents as it won't contain all named xcontent parsers but only the ones that provide and implement NamedXContentProvider.

The NamedXContentRegistry is usually instantiated once on the server or client side and it must contain all required named xcontent parsers. This list is potentially enriched with named xcontent parsers provided by modules or plugins that implement NamedXContentProvider. But in any case, the NamedXContentRegistry is instantiated once on server or client side with everything it needs.

On server side the registry is instantiated in the Node class and in the RestHighLevelClient class for an example of client side instanciation.

In your case, I guess you'll have to plumb the NamedXContentRegistry down to where you want to parse this content. I think it's also possible to retrieve a ref on the current registry from a XContentParser instance.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! As per our discussion, I have removed the automatic NamedXContentRegistry discovery and replaced with passing it to source(...) method as parameters.

@imotov
Copy link
Contributor Author

imotov commented Aug 14, 2018

retest this please

Copy link
Member

@dakrone dakrone left a comment

Choose a reason for hiding this comment

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

This LGTM, thanks Igor, I left a couple tiny comments.

@jasontedor might want to take a look and make sure this will be workable for CCR though

*/
public <T> boolean supportsNamedObject(Class<T> categoryClass, String name) {
Map<String, Entry> parsers = registry.get(categoryClass);
if (parsers == null || registry.isEmpty()) {
Copy link
Member

Choose a reason for hiding this comment

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

I think the registry.isEmpty() check is superfluous here

throw new IllegalArgumentException("No custom metadata prototype registered for type [" + type + "]");
public static Custom parseCustom(String name, Map<String, Object> map, DeprecationHandler deprecationHandler,
NamedXContentRegistry customsRegistry) throws IOException {
XContentBuilder customXContent = XContentFactory.jsonBuilder()
Copy link
Member

Choose a reason for hiding this comment

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

Can you put this in a try-with-resources block?

IndexMetaData.Custom custom = parser.namedObject(IndexMetaData.Custom.class, currentFieldName, null);
builder.putCustom(custom.getWriteableName(), custom);
} catch (UnknownNamedObjectException ex) {
logger.warn("Skipping unknown custom index metadata object with type {}", currentFieldName);
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be lowercase (suuuuper minor)

@dakrone
Copy link
Member

dakrone commented Aug 21, 2018

@jasontedor will this work for CCR's needs?

@dakrone dakrone added the WIP label Aug 28, 2018
@dakrone
Copy link
Member

dakrone commented Aug 30, 2018

Okay I've made some changes to this PR:

This PR nows removes the deprecated Custom class in IndexMetaData, in favor
of a Map<String, DiffableStringMap> that is used to store custom index
metadata. As part of this, there is now no way to set this metadata in a
template or create index request (since it's only set by plugins, or dedicated
REST endpoints).

The Map<String, DiffableStringMap> is intended to be a namespaced Map<String, String> (DiffableStringMap implements Map<String, String>, so the signature
is more like Map<String, Map<String, String>>). This is so we can do things
like:

Map<String, String> ccrMeta = indexMetaData.getCustom("ccr");

And then have complete control over the metadata. This also means any
plugin/feature that uses this has to manage its own BWC, as the map is just
serialized as a map. It also means that if metadata is put in the map that isn't
used (for instance, if a plugin were removed), it causes no failures the way
an unregistered Setting would.

The reason I use a custom DiffableStringMap here rather than a plain
Map<String, String> is so the map can be diffed with previous cluster state
updates for serialization.

/ping @martijnvg @jasontedor @s1monw as I believe you were all interested in the implementation of this

Copy link
Member

@martijnvg martijnvg left a comment

Choose a reason for hiding this comment

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

@dakrone This looks great! I left a couple of comments.

* This is a {@code Map<String, String>} that implements AbstractDiffable so it
* can be used for cluster state purposes
*/
public class DiffableStringMap extends AbstractDiffable<DiffableStringMap> implements Map<String, String> {
Copy link
Member

Choose a reason for hiding this comment

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

Can we extend from AbstractMap here instead of AbstractDiffable and then implement Diffable instead of Map? I think that would simplify this class and the only method that AbstractDiffable provides here is overwritten here.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1

Copy link
Member

Choose a reason for hiding this comment

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

Yep that definitely works, will do that

@SuppressWarnings("unchecked")
public <T extends Custom> T custom(String type) {
return (T) customs.get(type);
public Map<String, String> getCustomData(final String key) {
Copy link
Member

Choose a reason for hiding this comment

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

Are we returning here a mutable map? I don't think we should do that here. We should at least wrap it in Collections#unmodifiableMap()

Copy link
Member

Choose a reason for hiding this comment

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

Yep good idea, I'll do that

assertThat(dsm3.get("newkey"), equalTo("yay"));
assertThat(dsm3.get("baz"), equalTo("eggplant"));
assertThat(dsm3.get("potato"), equalTo(null));
}
Copy link
Member

Choose a reason for hiding this comment

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

Can we also add a test here with a few more maps and entries (maybe randomly generated) than in the existing test?

Copy link
Member

Choose a reason for hiding this comment

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

and maybe also a test that verifies that the serialization works?

Copy link
Contributor

Choose a reason for hiding this comment

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

I also wonder if we need a random test that adds randomly to one map and then passes it on as a diff to another and makes sure after we did that N times the maps are the same?

Copy link
Member

Choose a reason for hiding this comment

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

and maybe also a test that verifies that the serialization works?

The serialization was added as part of the IndexMetaDataTests, but I'll add one here for targeted serialization

Copy link
Member

Choose a reason for hiding this comment

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

I also wonder if we need a random test that adds randomly to one map and then passes it on as a diff to another and makes sure after we did that N times the maps are the same?

I'll add a test for that as well

Copy link
Contributor

@s1monw s1monw left a comment

Choose a reason for hiding this comment

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

Looks good in general. I left some minors and once @martijnvg comments are resolved I am good to go with that. nice and simple!

* This is a {@code Map<String, String>} that implements AbstractDiffable so it
* can be used for cluster state purposes
*/
public class DiffableStringMap extends AbstractDiffable<DiffableStringMap> implements Map<String, String> {
Copy link
Contributor

Choose a reason for hiding this comment

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

+1

if (in.getVersion().before(Version.V_7_0_0_alpha1)) {
// This used to be the size of custom metadata classes
int customSize = in.readVInt();
assert customSize == 0 : "unexpected custom metadata when none is supported";
Copy link
Contributor

Choose a reason for hiding this comment

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

hmm do we need to skip the size if we are in production? I mean that assert will not trip if we run without -ea

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 following, do you want it to be a regular exception instead of an assert? We still need to do the readVInt regardless of -ea or without, it's just the 0 size comparison that is an assert for tests

Copy link
Member

Choose a reason for hiding this comment

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

I think that we need to do a hard exception indeed, we won't be able to read the rest of the message at this point because we can't read the customs (no IndexMetaData#lookupPrototypeSafe anymore) when there are unexpected ones. That is, customSize > 0 is fatal for us.

assertThat(dsm3.get("newkey"), equalTo("yay"));
assertThat(dsm3.get("baz"), equalTo("eggplant"));
assertThat(dsm3.get("potato"), equalTo(null));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I also wonder if we need a random test that adds randomly to one map and then passes it on as a diff to another and makes sure after we did that N times the maps are the same?

org.elasticsearch.plugins.spi.NamedXContentProviderTests$TestNamedXContentProvider
Copy link
Contributor

Choose a reason for hiding this comment

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

this is an unrelated change?

Copy link
Member

Choose a reason for hiding this comment

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

Yep sorry, this was part of reverting Igor's changes that missed a newline

m2.put("newkey", "yay");
m2.put("baz", "eggplant");
DiffableStringMap dsm2 = new DiffableStringMap(m2);
logger.info("--> 1: {}", dsm);
Copy link
Contributor

Choose a reason for hiding this comment

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

can we remove the logging in this unittest?

Copy link
Member

Choose a reason for hiding this comment

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

Sure, will do this

Copy link
Member

@jasontedor jasontedor 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 one comment.

if (in.getVersion().before(Version.V_7_0_0_alpha1)) {
// This used to be the size of custom metadata classes
int customSize = in.readVInt();
assert customSize == 0 : "unexpected custom metadata when none is supported";
Copy link
Member

Choose a reason for hiding this comment

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

I think that we need to do a hard exception indeed, we won't be able to read the rest of the message at this point because we can't read the customs (no IndexMetaData#lookupPrototypeSafe anymore) when there are unexpected ones. That is, customSize > 0 is fatal for us.

if (in.getVersion().before(Version.V_7_0_0_alpha1)) {
// Used to be used for custom index metadata
int customSize = in.readVInt();
assert customSize == 0 : "expected not to have any custom metadata";
Copy link
Member

Choose a reason for hiding this comment

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

This should be fatal.

builder.putCustom(key, custom);
}
} else {
assert customSize == 0 : "expected no custom index metadata";
Copy link
Member

Choose a reason for hiding this comment

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

This should be fatal.

if (in.getVersion().before(Version.V_7_0_0_alpha1)) {
// Previously we allowed custom metadata
int customSize = in.readVInt();
assert customSize == 0 : "expected no custom metadata";
Copy link
Member

Choose a reason for hiding this comment

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

This should be fatal.

@dakrone
Copy link
Member

dakrone commented Aug 30, 2018

Thanks @jasontedor, I've addressed your comments

Copy link
Member

@jasontedor jasontedor left a comment

Choose a reason for hiding this comment

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

LGTM.

@dakrone dakrone merged commit 001b78f into elastic:master Aug 30, 2018
dakrone pushed a commit to dakrone/elasticsearch that referenced this pull request Aug 30, 2018
…32749)

This PR removes the deprecated `Custom` class in `IndexMetaData`, in favor
of a `Map<String, DiffableStringMap>` that is used to store custom index
metadata. As part of this, there is now no way to set this metadata in a
template or create index request (since it's only set by plugins, or dedicated
REST endpoints).

The `Map<String, DiffableStringMap>` is intended to be a namespaced `Map<String,
String>` (`DiffableStringMap` implements `Map<String, String>`, so the signature
is more like `Map<String, Map<String, String>>`). This is so we can do things
like:

``` java
Map<String, String> ccrMeta = indexMetaData.getCustom("ccr");
```

And then have complete control over the metadata. This also means any
plugin/feature that uses this has to manage its own BWC, as the map is just
serialized as a map. It also means that if metadata is put in the map that isn't
used (for instance, if a plugin were removed), it causes no failures the way
an unregistered `Setting` would.

The reason I use a custom `DiffableStringMap` here rather than a plain
`Map<String, String>` is so the map can be diffed with previous cluster state
updates for serialization.

Supersedes elastic#32683
}

@Override
public boolean equals(Object obj) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think it is necessary to overwrite equals(), hashCode() and toString() here? AbstractMap already implements these methods.

@martijnvg
Copy link
Member

LGTM - left one minor comment.

dnhatn added a commit that referenced this pull request Sep 1, 2018
* 6.x:
  Mute test watcher usage stats output
  [Rollup] Fix FullClusterRestart test
  TEST: Disable soft-deletes in ParentChildTestCase
  TEST: Disable randomized soft-deletes settings
  Integrates soft-deletes into Elasticsearch (#33222)
  drop `index.shard.check_on_startup: fix` (#32279)
  Fix AwaitsFix issue number
  Mute SmokeTestWatcherWithSecurityIT testsi
  [DOCS] Moves ml folder from x-pack/docs to docs (#33248)
  TEST: mute more SmokeTestWatcherWithSecurityIT tests
  [DOCS] Move rollup APIs to docs (#31450)
  [DOCS] Rename X-Pack Commands section (#33005)
  Fixes SecurityIntegTestCase so it always adds at least one alias (#33296)
  TESTS: Fix Random Fail in MockTcpTransportTests (#33061) (#33307)
  MINOR: Remove Dead Code from PathTrie (#33280) (#33306)
  Fix pom for build-tools (#33300)
  Lazy evaluate java9home (#33301)
  SQL: test coverage for JdbcResultSet (#32813)
  Work around to be able to generate eclipse projects (#33295)
  Different handling for security specific errors in the CLI. Fix for #33230 (#33255)
  [ML] Refactor delimited file structure detection (#33233)
  SQL: Support multi-index format as table identifier (#33278)
  Enable forbiddenapis server java9 (#33245)
  [MUTE] SmokeTestWatcherWithSecurityIT flaky tests
  Add region ISO code to GeoIP Ingest plugin (#31669) (#33276)
  Don't be strict for 6.x
  Update serialization versions for custom IndexMetaData backport
  Replace IndexMetaData.Custom with Map-based custom metadata (#32749)
  Painless: Fix Bindings Bug (#33274)
  SQL: prevent duplicate generation for repeated aggs (#33252)
  TEST: Mute testMonitorClusterHealth
  Fix serialization of empty field capabilities response (#33263)
  Fix nested _source retrieval with includes/excludes (#33180)
  [DOCS] TLS file resources are reloadable (#33258)
  Watcher: Ensure TriggerEngine start replaces existing watches (#33157)
  Ignore module-info in jar hell checks (#33011)
  Fix docs build after #33241
  [DOC] Repository GCS ADC not supported (#33238)
  Upgrade to latest Gradle 4.10  (#32801)
  Fix/30904 cluster formation part2 (#32877)
  Move file-based discovery to core (#33241)
  HLRC: add client side RefreshPolicy (#33209)
  [Kerberos] Add unsupported languages for tests (#33253)
  Watcher: Reload properly on remote shard change (#33167)
  Fix classpath security checks for external tests. (#33066)
  [Rollup] Only allow aggregating on multiples of configured interval (#32052)
  Added deprecation warning for rescore in scroll queries (#33070)
  Apply settings filter to get cluster settings API (#33247)
  [Rollup] Re-factor Rollup Indexer into a generic indexer for re-usability   (#32743)
  HLRC: create base timed request class (#33216)
  HLRC: Use Optional in validation logic (#33104)
  Painless: Add Bindings (#33042)
@jimczi jimczi added v7.0.0-beta1 and removed v7.0.0 labels Feb 7, 2019
@imotov imotov deleted the remove-proto-from-indexmetadata-custom-2 branch May 1, 2020 22:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Data Management/Indices APIs APIs to create and manage indices and templates >refactoring v6.5.0 v7.0.0-beta1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants