-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
Separate out validation of groups of settings #34184
Separate out validation of groups of settings #34184
Conversation
Pinging @elastic/es-core-infra |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs some tests adding. The one I suggested would be valuable, but I think it also needs something that focusses on this change specifically, so that this remains covered even if the disk watermark feature were changed and that test were lost.
@elasticmachine test this please |
Sure, I'll report the test you've suggested and add a new one on this change in particular, thank you @DaveCTurner. |
Note that the build that failed looks like it failed for good reason. Could you move this failure into a unit test and then fix it? |
Yes 👍 |
Issue found and fixed in f77d1e7, thanks @DaveCTurner. I'm now focusing on implementing relevant test cases. |
Responding to #28309 (comment) here since this is the right place to discuss this PR. The single test present in this PR (the current head that I'm looking at is f77d1e7) does not support the change to the production code, in the sense that it passes on today's The test failure that you found and fixed should also be captured in a test. Additionally I think we should have a test that focusses on this change to the way that Settings work which is independent of the disk watermark feature. |
I agree with you @DaveCTurner, tests are under development. Let me please reword: in my previous comment, I meant I've modified the test you suggested and I wanted to know whether or not you agree with the changes I've made in your test. Do you see what I mean? |
I think so, and my feedback was that this test passes on If there is more work in progress then it's probably best to ping when the whole PR is ready to review - it'll be easier to see the whole picture. |
I understand, thank you. |
@DaveCTurner PR is complete and ready for review. |
I've found another bug in the I've added commit 68044ba to this PR to fix it and I've added a test case to cover this change. PR is ready for review, I've nothing more to add, thank you. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @cbismuth, this is a tricky area and you've done some good work here. I've left a few comments and suggested an alternative approach that I think would be better, although I haven't tried it so you're welcome to describe why it doesn't work :)
final ClusterSettings clusterSettings = new ClusterSettings(settings, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS); | ||
new DiskThresholdSettings(settings, clusterSettings); // this has the effect of registering the settings updater | ||
|
||
settings = clusterSettings.applySettings(Settings.builder() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see what's happened here, and I must apologise. The unit test I suggested was looking at applySettings
but really I should have used updateSettings
since this is where the update is calculated. My bad.
However, this test still passes on master
without any changes so I don't think it should be added here. Could you make an appropriate test based around updateSettings
instead, and check that it fails on master
and passes on this branch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries at all. Sure, I'll update the test and run it on this branch and on master
.
updates, | ||
"transient" | ||
); | ||
assertFalse(updated); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's ok to return true
here. Claiming to have updated a setting when really it hasn't changed in value is a right-side failure: it means we call reroute()
to reallocate any shards affected by the new settings, but this does nothing if the setting change was a no-op. On the other hand failing to call reroute()
when a setting does change is very bad, so changing the behaviour here needs a lot more scrutiny.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand. I'll restore the previous behavior to not break the current data flow and leave this responsibility to downstream components, it's safer.
I wasn't very at ease with this change, thank you for catching it. I'll dig deeper into it with a debugger just for my own curiosity.
); | ||
assertTrue(updated); | ||
assertThat(target.get(SETTING_FOO_LOW.getKey()), equalTo("20")); | ||
assertThat(updates.get(SETTING_FOO_LOW.getKey()), equalTo("20")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can write this with fewer lines and fewer mutable variables by putting each of these steps into its own block like this:
{
final Settings.Builder updates = Settings.builder();
assertTrue(service.updateSettings(Settings.builder().put(SETTING_FOO_LOW.getKey(), 20).build(), target, updates, "transient"));
assertThat(target.get(SETTING_FOO_LOW.getKey()), equalTo("20"));
assertThat(updates.get(SETTING_FOO_LOW.getKey()), equalTo("20"));
}
The line-width limit is 140 characters, and this fits into that very neatly. It's something of a matter of taste, but we seem to prefer denser code in this sort of situation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine for me. The more immutable code is, the more confident I feel.
Settings.builder(), | ||
"transient" | ||
)); | ||
assertThat(exception.getMessage(), equalTo("[high]=10 is lower than [low]=20")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we also have a case that shows that if low
is set below 10
then high
can also be set below 10
, to verify that we're not validating against both the default and the actual values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I'll add these test cases.
import static org.hamcrest.Matchers.hasToString; | ||
import static org.hamcrest.Matchers.sameInstance; | ||
|
||
public class ScopedSettingsTests extends ESTestCase { | ||
|
||
private static class FooLowSettingValidator implements Setting.Validator<Integer> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these settings can reasonably be moved right next to testUpdateOfValidationDependantSettings
since that's the only place they're used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that would be better, I'll move them.
changed = true; | ||
changed = hasChanged(toApply, target); | ||
if (changed) { | ||
validate(toApply, target.build()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this isn't quite the right approach. As I understand it this change means we're validating the whole settings update here, but by my reading we already validate that later on in the process:
Lines 94 to 95 in 4dc3ada
clusterSettings.validate(transientFinalSettings, true); | |
clusterSettings.validate(persistentFinalSettings, true); |
I think it'd be better to try and do less validation here on settings that have dependent settings. Perhaps the Setting.Validator
interface should have two validate()
methods, one for this stage of validation (good for settings with no dependencies, i.e., most of them) and one for the full validation including dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I see what you mean. I'll try to move the full validation including dependencies in a new API in Setting.Validator
. I also think it's a better location for this.
|
||
private boolean hasChanged(Settings toApply, Settings.Builder target) { | ||
boolean changed = toApply.keySet().stream().anyMatch(k -> k.endsWith("*")); | ||
if (!changed) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The house style is to avoid using the !
unary operator and to prefer saying changed == false
here, because !
is small and easy to miss in a long expression, and == false
is larger and therefore more visible. I think this code will go away due to other comments, but thought I'd raise it anyway for your info.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sharing this guideline with me, it's indeed more readable without the unary operator.
Thank you @DaveCTurner for the kind words and insightful review. I'll rework my changes and let you know when it's ready. |
…parently_valid_sequence_of_updates
…parently_valid_sequence_of_updates
…parently_valid_sequence_of_updates
…parently_valid_sequence_of_updates
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM I left a suggestion regarding the interface
* | ||
* @param value the value of this setting | ||
* @param settings a map from the settings specified by {@link #settings()}} to their values | ||
*/ | ||
void validate(T value, Map<Setting<T>, T> settings); | ||
default void validate(T value, Map<Setting<T>, T> settings) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does it make sense to delegate to validate(T value)
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from an interface perspective I think we might be able to only have validate(T value, Optional<Map<Setting<T>, T>> settings)
and instead check the optional if needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @s1monw.
I'd prefer to keep these two API as most clients will only be interested in validating a setting in isolation (i.e. not against dependencies).
Besides, we do a fail-fast validation here where we don't have resolved setting dependencies yet. Therefore, if we want to keep this fail-fast shortcut, any client will have to implement an if optional present/absent
block even if he's interested in validating a setting against its dependencies.
What do you think about these two points?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I prefer the two methods to the optional parameter too. In practice there are very few dependencies between settings, so the one-argument lambda is less noisy in almost all cases. Settings with dependencies can't use a lambda anyway because they have to override the settings()
function too.
I also think there's no need for the dependent validation to delegate to the other one, because we call both during settings validation. However I think this should be mentioned in the Javadocs as implementors shouldn't need to go to the implementation to check this (and I know I'd forget and have to check). Also the interface-level comment needs updating as it's no longer wholly accurate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @DaveCTurner, here is a additional commit 7c33bfe to improve the Validator
class documentation, thank you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have strong feelings but if I see two methods I'd only implement one and don't know what I need to do with the other. The optional makes me think about the opportunity to validate against others. I don't know what you mean by clients
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant developers who will implement this interface.
We tried to make javadoc more explicit, but I see what you mean: the API doesn't enforce the developer to ask himself whether or not he would like to implement dependencies validation. But as it's an edge case, I still hope the developer would read the javadoc or jump to the interface definition if he needs to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another alternative would be to remove the validation shortcut and keep only the validate(T value, Map<Setting<T>, T> settings)
interface API, I think that would make sense.
We would remove this validation shortcut here and keep only this validation call here (and remove this line).
What do you think about it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a comment about Javadocs using the wrong button -> https://github.com/elastic/elasticsearch/pull/34184/files#r234140602
…parently_valid_sequence_of_updates
…parently_valid_sequence_of_updates
…parently_valid_sequence_of_updates
Hi @s1monw and @DaveCTurner, I'm sure everyone is quite busy, so here is a quick follow up below to ease PR validation and merge.
We have three options:
I would prefer option 3, and you? |
…parently_valid_sequence_of_updates
We have a green build 👍 |
Hi, I know it's Wednesday and there're probably many things to close before the weekend. I hope next week we will be able state whether or not this PR can be merged as it is (with latest documentation fix included) or should we go for a new iteration to simplify the The more I look at it, the more I think this version is a good one and we should merge it as it is. |
Hi @DaveCTurner, what do you think we should do on this PR? Thanks. |
…parently_valid_sequence_of_updates
Hi @cbismuth, just to let you know that this PR is still in my queue. Bear with me, I have a few too many things in flight right now. |
No worries, thank you @DaveCTurner, I'll be available when needed. |
…parently_valid_sequence_of_updates
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's great! Thanks a lot @DaveCTurner for finely reviewing the code and no worries at all for the time it took. |
Today, a setting can declare that its validity depends on the values of other related settings. However, the validity of a setting is not always checked against the correct values of its dependent settings because those settings' correct values may not be available when the validator runs. This commit separates the validation of a settings updates into two phases, with separate methods on the `Setting.Validator` interface. In the first phase the setting's validity is checked in isolation, and in the second phase it is checked again against the values of its related settings. Most settings only use the first phase, and only the few settings with dependencies make use of the second phase.
…piration * elastic/master: Removing unused methods in Numbers (elastic#37186) Fix setting by time unit (elastic#37192) [DOCS] Cleans up xpackml attributes ML: fix delayed data annotations on secured cluster (elastic#37193) Update version in SearchRequest and related test [DOCS] Adds overview and API ref for cluster voting configurations (elastic#36954) ML: changing JobResultsProvider.getForecastRequestStats to support > 1 index (elastic#37157) Separate out validation of groups of settings (elastic#34184)
This pull request allows a setting to be validated against target settings to ensure runtime dependencies are met (e.g. disk watermarks
low
,high
andflood_stage
).This pull request also includes a fix to not update settings when no value has changed.