-
Notifications
You must be signed in to change notification settings - Fork 507
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
HDDS-11149. Have a generic version Validator for validating Requests #6932
base: master
Are you sure you want to change the base?
Conversation
…or to validate clients older than the specified version Change-Id: Ifcbb239994e7357032934d104ab18063c8d64251
Creating this patch as permanent fix in favour of #6922 |
If this approach looks good, I can also go about adding an integration test which tests all Om operations for different client versions. |
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 @swamirishi for making the long-term fix quickly. :)
Only one comment so far (but it applies to many files).
conditions = {}, | ||
processingPhase = RequestProcessingPhase.PRE_PROCESS, | ||
requestType = Type.CreateBucket | ||
requestType = Type.CreateBucket, | ||
maxClientVersion = ClientVersion.ERASURE_CODING_SUPPORT |
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 would add {}
as default value here:
Line 85 in bde95f7
ValidationCondition[] conditions(); |
and tweak changes in @RequestFeatureValidator
usage:
- conditions = ValidationCondition.OLDER_CLIENT_REQUESTS,
+ maxClientVersion = ClientVersion.ERASURE_CODING_SUPPORT,
to reduce the patch. This changes only 1 line per item, instead of 3 lines + an unchanged line in the middle.
Looks like I commented without refreshing the PR, this seems to follow same approach as what is suggested as long term solution. Thanks @swamirishi for the quick fix. |
...e-manager/src/main/java/org/apache/hadoop/ozone/om/request/validation/ValidatorRegistry.java
Outdated
Show resolved
Hide resolved
Change-Id: Ife98bca526abf9d82d383f7e65f4140ba73e21a2
Entry<? extends ExecutableElement, ? extends AnnotationValue> entry) { | ||
if (isPropertyNamedAs(entry, ANNOTATION_MAX_CLIENT_VERSION_PROPERTY_NAME)) { | ||
Name maxClientVersion = visit(entry, new MaxClientVersionVisitor()); | ||
return !maxClientVersion.contentEquals(MAX_CLIENT_VERSION_FUTURE_VERSION); |
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 also check for sanity of maxClientVersion if not equal to MAX_CLIENT_VERSION_FUTURE_VERSION i.e whether it lies in the same range as ClientVersion.values()
Oh we can't do that since this is part of the static code. The annotation processor also doesn't have any access to the enums. So if we add this check, everytime we change something in the enum,. We would have to make a change. Thus bringing us back to the same problem. |
Please give me some time to get back to this one next week... I gonna need to digest this a bit more and also I would like to dig out some of my notes on earlier plans about how to make this better. I would not rush to commit this, so if it is required to fix this, let's push #6922 if we have time to work out this solution then please wait for me a bit. 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 for working on this @swamirishi. While this PR is focused on the issues with the OLDER_CLIENT_REQUEST
condition, the CLUSTER_NEEDS_FINALIZATION
condition has the same problem. I suggest this:
- Adding two new keys in the annotation: one for client version and one for layout version. They are exclusive. Only one is allowed to be specified per annotation.
- Removing the general
ValidationCondition
from the annotation. - Two 3-nested maps in the registry: one for layout feature validators and one for client version validators since the version enums have different types.
Currently there is some iteration required for every request, even if they don't have a validator. With the general concept of validation conditions removed we can do a simple map lookup which will be more efficient in the common case, especially if we order the keys accordingly.
In this layout I think request version -> request type -> request phase -> validator
is the most efficient layout of the map. Most clients will probably be newer versions and can fail out of the check in the first level. After that, most requests will not have compat concerns for a given version, so even more requests exit early. The phase is hardcoded depending on which method is called so there is no option to exit early here, hence it should be last.
This is less general than the validation condition support we have now, but IMO the general implementation is causing code complexity and inefficiencies on the write path for something we do not use and may never need. This also makes it consistent with other upgrade related annotations like DisallowedUntilLayoutVersion
which only take a version and act on it with a fixed condition.
EDIT: I just remembered client version is the only thing that is not an exact match, so using it as a key directly will not work. The idea of baking the conditions into the framework to simplify the implementation and make it more consistent with the layout feature finalization framework still seems like a good approach though.
processingPhase = RequestProcessingPhase.PRE_PROCESS, | ||
requestType = Type.CreateBucket | ||
requestType = Type.CreateBucket, | ||
maxClientVersion = ClientVersion.ERASURE_CODING_SUPPORT |
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 should make this so that ClientVersion.BUCKET_LAYOUT_SUPPORT
is provided in the annotation for validators that deal with bucket layout. That would make this an exclusive upper bound on the client version that needs to be processed instead of an inclusive one.
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.
Also we can remove the if
statements in the body now right?
@@ -81,7 +82,7 @@ | |||
* Runtime conditions in which a validator should run. | |||
* @return a list of conditions when the validator should be applied | |||
*/ | |||
ValidationCondition[] conditions(); | |||
ValidationCondition[] conditions() default {}; |
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.
Do we still need this field?
* conditions though. | ||
* @returns the max required client version for which the validator runs for older clients. | ||
*/ | ||
ClientVersion maxClientVersion() default ClientVersion.FUTURE_VERSION; |
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.
If we remove the conditions
field then this probably shouldn't have a default value. Technically the request validators can be used for things other than client versioning but I don't think we have a use case for that right now. Narrowing the scope of conditions indicates what their intended use is.
private final Map<Pair<Type, RequestProcessingPhase>, Pair<List<Method>, TreeMap<Integer, Integer>>> | ||
maxAllowedVersionValidatorMap = new HashMap<>(Type.values().length * RequestProcessingPhase.values().length, | ||
1.0f); |
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.
If we get rid ValidationCondition
then we just have 3 keys to identify the validator(s) that need to run:
- Request type
- Request phase
- Client version
A triple nested map of these keys will be much easier to follow than the wild data structure created here.
Version shouldn't be part of the map key? We need the validator running for all clients older than a certain condition.
|
Change-Id: Ib1742e6a78f908598021ed0bb80f71d3c1527735
Change-Id: Ic084d8bc456468d66cb26d8fc0cfdd33920fb8ed # Conflicts: # hadoop-hdds/common/src/main/resources/META-INF/services/javax.annotation.processing.Processor
Change-Id: Iab5e022b6a1401998d905c68d491fe2a2a7413b7
Change-Id: Iba9e1caa6a66b033d39c95b20f78ebc8e23fba4d
Change-Id: I05265cad5f26082351c6926c24866b20a50b26d2
Hi @swamirishi, thank you for continued work on this one, the idea presented in the PR seems really promising, and for the first sight, it is the generalisation in the proper direction in order to make it possible to extend the current request validations to other service components, which I really like. To be honest I really envy you to get to this, I never forgot how badly I wanted to push things towards this direction back at the time when the original feature was ready but we could not spare time for this... Anyway... let's go back to the review itself... Please note that the single most crucial point of this whole system is the validationsFor() methods in ValidatorRegistry, every other bit is secondary compared to the operational effectiveness of how we get the validations, as that is in the way of all request processing. This means optimally the system can figure out at the very first step the cheapest possible way that the there are no validations to apply to a request, as in almost all request will fall into this bucket. Besides that the data structure should be designed in a way that it does not hold back the request processing more than it is absolutely necessary when there are version differences between the client and the server. I still did not have the time to digest fully the changes proposed, I wanted to give feedback as early as possible even if it is partial. You can expect some more review comments from me after I have the time to understand things further, most likely just early next week unfortunately. On the other hand, there is a problem which we need to address. A less burning issue, however it is our best interest to deal with this: |
Change-Id: I22ff08e5f4c43cbdda7a7db5f4d83417b55d481e
Change-Id: I551cb26f648bfedb7bc5bfee63b34731b56b4d9b
@swamirishi
cc: @fapifta |
Change-Id: I5fb15b4e3559fcee51f1964a4b0a50c57222f409
Hi @sumitagrawl Thank you for the suggestions, let me add some answers and further ideas for them.
PreFilter and PostFilter are a long standing part of Spring, however in J2EE we only have the Filter as part of the servlet specification. In our case we do something similar, but still some fundamentally different. I am not particularly fan of the original name either of the current OMLayoutVersionValidator or ClientVersionValidator either. But PreFilter and PostFilter in itself for me is also just a name that is close but somehow meh a bit. Let me give some history to the naming as I remember things. Once the original system was ready we already knew that there are a good couple of things that are not suitable for other components, and this have hurt us when we needed to solve compatibility in other components also for the same release. Back than we had some discussions about how to do this transitively for clients that are connecting to OM, but then OM also connects to SCM to fulfil the request. We also have had discussions about exotic and less exotic race conditions and what problems might come from them and how we can solve these, and most importantly we talked about how to extend the system to other components.
The problem of pre-finalized validations is the following (and you can check this for bucket layouts for example):
Priority is a thing as I mentioned we put some thoughts into already, however at that point we have not implemented it, as we did not see the use of it in the context of 1.2->1.3 upgrade support. In that context EC and Bucket layout support was two features added within the same version, so ordering the validations would not meant any difference in the overall processing because these two had to be validated anyway, and if one validation passes the other might still fail, while the use of any of them was not predicted to be more common overall. As of now even if we argue that an upgrade can now jump more than one release version, we have the possibility to order the validations based on their maxVersion, which naturally prioritizes older validators first, and still serves validators for the same release in an ad-hoc way. I do believe there is no use case where priority would be needed at least I am yet to see one, and once I saw one I am happy to revisit my stance on this.
I would argue that we have a terminology problem here... All in all:
Besides all of this we have to talk about possible race conditions with the system we are already aware of, but we left as is back than, and we still did not solved, like the one in HDDS-6682. As that may also reveal new problems and new ideas to simplify or complicate the current logic. |
processingPhase = RequestProcessingPhase.PRE_PROCESS, | ||
requestType = Type.CreateDirectory | ||
requestType = Type.CreateDirectory, | ||
maxVersion = ClientVersion.ERASURE_CODING_SUPPORT |
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 should be BUCKET_LAYOUT_SUPPORT as max version
@fapifta requestType can be array for requests to be supported for this... |
Change-Id: If7c47ea7242cfac29934c1a0eae6c57a46ccea58
@sumitagrawl thank you for giving an example for a generic pre-finalized state validation. The problem with this is that we do not want to disable all operations that have a new feature in the pre-finalized state, but we want to prevent using any new feature, so that if tests shows that a rollback/downgrade is needed, then it can be done. So in the pre-finalized state what we need is to disable any new features coming in, that would store metadata that the old code does not understand, but we want to ensure that all things are operational so that it is possible to verify that the upgraded software works as expected before allowing to write data/metadata that the old code can not understand. |
for (RequestType type : types) { | ||
EnumMap<RequestType, EnumMap<RequestProcessingPhase, IndexedItems<Method, Integer>>> requestMap = | ||
this.indexedValidatorMap.get(versionClass); | ||
EnumMap<RequestProcessingPhase, IndexedItems<Method, Integer>> phaseMap = |
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.
ApplyUntilVersion is of integer value type. This represents 2 type version enum version:
- Client Version
- Layout Version
Using integer, it can not represent both and there is chance of picking wrong method. So we need have either separate storage for each type or some way to distinguish them while filtering during validation.
cc: @fapifta
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 indexedValidatorMap has different mappings for different version classes at the top level, that makes the distinction possible.
The background of this is more interesting though...
We have a proto definition that defines messages between a server and a client. In this case the server is the OM, while the client is the Ozone client. Both of these parties have a version associated with them, in this case the ClientVersion, and the OmLayoutVersion.
Both sides can be aware of the corresponding version of the other side within its release version (though afaik the Ozone client does not care or may not know about the OmLayoutVersion as we did not handle compatibility from that side).
Now...
When an older client sends a request to a newer server, then the server has its client version hardcoded in its codebase, and compares that with the client version in the request. If the client version is different, it selects the validators that are required for the client version supplied in the request from the client version type related mappings.
On top of this, the server knows is hardcoded server version, and its finalized server version, and if there is a difference between the two, then we are in a pre-finalized state, and it selects the validators that has to be applied for the finalized server version it has.
When a newer client sends a request, the client version translates to FUTURE_VERSION, as the older server does not have the new client version enum value. In this case it does not apply any validators on the client version, but it still applies server version validators if there are any non-finalized server versions. (Like in a case where server has a version released in Ozone 3.2, the client is using code released with version 3.3, while the server side was just updated from 3.1 to 3.2.)
Why we have validations for both the server and the client version?
There may be changes in the metadata layout on the server side that does not affect the client side, so the client version is not bumped, but the server version is bumped. In this case we need the server side version to properly finalize things.
But on the other hand after the finalization, we need to validate the client version, as older clients may not understand data returned in the response, as that may contain newly added things. In this case we either need to send an exception to the client that informs the client about the inability to serve the response to an old client, or we need to translate the data so that the old client understands. This part we can not spare, and after finalizing an upgrade old clients are affected.
So that is why we have a different set of validators based on versions, as one case is to handle older clients, the other is to handle the pre finalized state.
In the OM and Ozone client relation for which we have the system set up, OMClientVersionValidator is dealing with old clients, while the OMLayoutFeatureValidator deals with the pre-finalized state of OM.
@fapifta I have gone through this in details, and understood the use case of this.
Perform 2 type of validation / processing
IMO, applyUntil means version is already mapping to the expected version. |
Also,
Check for isAllowed() / throw exception is redundant for every method adding the validation. Can the code be simplified by having specific annotation? // Below for validation, support or not, if not support, throw exception // Below one for processor, updating request to support compatibility, like removal of replication config. |
@sumitagrawl, Yes, it will affect startup performance, and also has some memory footprint. Let say we have 1000 validators, we may use 10MB tops for this data structure but I would guess less than that... however this is just a wild guess... even if it is 100MB we probably are fine, as we are able to directly address all validators we need within the mappings. Computational performance? It may or may not suffer, it depends, how many old client requests are coming in, how many new features and layout changes were introduced with the last release and so on. The decision on this is quick, and once done, it is clear which validators we need if we need any. These annotated methods potentially have some retention time, so the number is not monotonically growing, as if we announce dropping compatibility support for example for anything before 1.5 in 2.0 and we require all clusters to be upgraded to 1.5 before it can be upgraded to 2.0, we can simply remove anything that belongs to a a version that is prior to 1.5, except the actual client and server side validations, with that decreasing the number of validators over time. |
Should we implement this in steps to avoid too big changes and difficult to review patches? |
Yeah let me see if I can break it into smaller patches. |
Avoid server version validator if the server has already been finalized & also can explore skipping registering validators if the server starts up in a finalized state. |
Change-Id: I2d19e9052abe0702f6fe391d32737a53601852b1
Change-Id: I39af5db40fa96fee27ef53ab7ef5f44002736e4e # Conflicts: # hadoop-hdds/tools/pom.xml # hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/file/OMDirectoryCreateRequest.java # hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCommitRequest.java # hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/key/OMKeyCreateRequest.java # hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java # hadoop-ozone/tools/pom.xml
…0983 (apache#7348)" This reverts commit e7bf154 Change-Id: Ie8d5b1ec85844b890039eb0026fad90fa4d45601
Change-Id: I9f6854136551102dfe94086a8afaf1c820331533
Change-Id: Ia0d97078d6c9fc61f2f79bc322f966e11ab4a3c4
Change-Id: I63b013066618c0568e13c2323b8403d1ca6d0426
Change-Id: I7701ad25c3fb4cde9f78daef3053c822b41fe4a0
Change-Id: I855a19c67256a27504b7e3d134b2f500a94bf330
Change-Id: I6a5e84b8053a9401ce4210849495af8254561f09
Change-Id: I13bc9e28c5db3b706265abb8cecaf71c5201151c
Change-Id: I8302abcfcaa70b07a85ef9505c7dabdc312955d4
Change-Id: I9b30d241a216733266754086b2add6fd623f27dd
Change-Id: I08b43ff6a366ad4744110512ccd0109e16701d6b
What changes were proposed in this pull request?
Currently RequestFeatureValidator only has a validator which checks if a certain client version for the OM request made by an older client or if the OM needs to be finalized.
There is a need for a more generic validator which should have a capability to validate the request from the client based on the server's layout version & client's version.
The patch introduces a new annotation RegisterValidator which allows to register a new feature validator for any request. The annotation enforces the validator to have a RequestType, maxVersion, ProcessingPhase in it's definition.
ValidationRegistry should be able to pull the validators registered and inturn should be able to get all the underlying validator methods based on the usage of the registered feature validator. The patch segregates the ClientVersionValidation and FINALIZATION validation into 2 validators named OMClientVersionValidator & OMLayoutVersionValidator respectively which would be responsible for checking the request client version & server side metadata layout version.
The validator has been written in such a way that the same code can be reused for other components (SCM & DN) as well to do their request validations as well.
What is the link to the Apache JIRA
https://issues.apache.org/jira/browse/HDDS-11149
How was this patch tested?
Existing unit test and additional unit tests