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

Deserialize WatchEvents using the specific object type #3786

Merged
merged 4 commits into from
Feb 1, 2022

Conversation

andreaTP
Copy link
Member

@andreaTP andreaTP commented Jan 25, 2022

Description

This is a proposal to fix the specific issue reproduced in #3785

Instead of relying on the KubernetesDeserializer fallback mechanism here we are trying to deserialize the WatchEvents using the specific deserializer for the type.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change
  • Chore (non-breaking change which doesn't affect codebase;
    test, version modification, documentation, etc.)

Checklist

  • Code contributed by me aligns with current project license: Apache 2.0
  • I Added CHANGELOG entry regarding this change
  • I have implemented unit tests to cover my changes
  • I have added/updated the javadocs and other documentation accordingly
  • No new bugs, code smells, etc. in SonarCloud report
  • I tested my code in Kubernetes
  • I tested my code in OpenShift

cc. @shawkins

@sonarcloud
Copy link

sonarcloud bot commented Jan 26, 2022

SonarCloud Quality Gate failed.    Quality Gate failed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

34.5% 34.5% Coverage
0.0% 0.0% Duplication

@andreaTP andreaTP marked this pull request as ready for review January 26, 2022 14:37

private WatchEvent contextAwareWatchEventDeserializer(String messageSource) {
try {
return Serialization.unmarshal(messageSource, WatchEvent.class);
Copy link
Member Author

Choose a reason for hiding this comment

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

If the current deserialization mechanism fails we fallback to try to deserialize the more specific class

updateResourceVersion(obj.getMetadata().getResourceVersion());
Action action = Action.valueOf(event.getType());
eventReceived(action, obj);
if (object instanceof Status) {
Copy link
Member Author

Choose a reason for hiding this comment

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

This is just a little bit of reordering:

  • deserializing the specific type is "more likely" to end up with something that extends HasMetadata
  • Status will be checked first

setupMockServerExpectations(Animal.class, "ns1", this::getList, r -> new WatchEvent(getAnimal("red-panda", "Carnivora", r), "ADDED"), null, null);

// When
KubernetesDeserializer.registerCustomKind("jungle.example.com/v1","Animal", CronTab.class);
Copy link
Member Author

Choose a reason for hiding this comment

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

This is a little "extreme", instead of registering an incompatible version I'm registering a completely wrong deserializer that will take precedence; but it shows the behaviour described in #3785

@andreaTP
Copy link
Member Author

@shawkins this is ready for review now, managed to reproduce the issue in a test case.

@shawkins
Copy link
Contributor

@shawkins this is ready for review now, managed to reproduce the issue in a test case.

My concern is that this is an invalid situation to start off with, so it seems like this handling is not generally needed.

@andreaTP
Copy link
Member Author

andreaTP commented Jan 26, 2022

I can argue that watching GenericKubernetesResources events should not break if you register a random serializer for totally different purposes.
This PR specifically fixes this issue.

If you want I can include a more idiomatic test showing this situation.

@shawkins
Copy link
Contributor

I can argue that watching GenericKubernetesResources events should not break if you register a random serializer for totally different purposes.

This would not occur if the resources you were getting back conformed to their expected schema. The general question - is that an expected case? If the consensus is that we need to guard against it - then we need to think more broadly. Does that only apply to the generic inform case? Because from what I can see once you create the v2 resource, any usage of the v1 typed api (or subsequent) generic will be broken. For example in your original test case, once the value exists try doing a list operation using the typed v1 api - it will fail.

@andreaTP
Copy link
Member Author

andreaTP commented Jan 26, 2022

This would not occur if the resources you were getting back conformed to their expected schema.

This is not completely correct, when you change the storageVersion the Kubernetes API is going to return "invalid" objects, e.g. with version inconsistent with the payload.

This is the use-case that I was trying to support:
operator-framework/java-operator-sdk#874

and this is the example project that I'm targeting with those modifications:
https://github.com/andreaTP/josdk-multiple-crd-versions-example

My expectation is not having two working controllers, the old controller can e.g. simply log the fact that a CR has been modified (and the situation will be eventually handled by WebHooks).
But I'd expect the underlying layers (JOSDK. and kubernetes-client) to not break in this condition.

@shawkins
Copy link
Contributor

This is not completely correct, when you change the storageVersion the Kubernetes API is going to return "invalid" objects, e.g. with version inconsistent with the payload.

Only if you choose not to have a conversion strategy.

(and the situation will be eventually handled by WebHooks).

You're referring to the conversion webhooks right? https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#overview - if they were in place wouldn't this change be moot? Wouldn't it be a best practice to have that in place anytime there is a breaking schema modfication?

@andreaTP
Copy link
Member Author

You're referring to the conversion webhooks right? https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#overview - if they were in place wouldn't this change be moot? Wouldn't it be a best practice to have that in place anytime there is a breaking schema modfication?

It's best practice, but, you cannot guarantee the webhooks to be in place.
I do believe that this library should be agnostic and support this valid use-case/situation gracefully.

This comes extremely useful for handling controlled upgrades procedures, such as Canary releases etc.

@shawkins shawkins self-requested a review January 26, 2022 18:39
Copy link
Contributor

@shawkins shawkins left a comment

Choose a reason for hiding this comment

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

I do believe that this library should be agnostic and support this valid use-case/situation gracefully.

Sorry I wasn't seeing that all other generic usage is fine because it's rooted by parsing from a class that doesn't reference the KubernetesDeserializer. The only places that will have problems are from classes, like the WatchEvent, which do reference the KubernetesDeserializer and no way to control the type of contained resources. WatchEvent is probably the only affected place. I don't think within fabric8 we'll try to independently parse things like ControllerRevision or other resources that contain references to HasMetadata or KubernetesResource under the generic api.

So the understanding here is that any continued usage of the typed api will break (which is not a fabric8 specific problem), but you'd like the generic watches/inform to continue to work even if the values are invalid for the reported version.

I think your solution is fine for that. There's probably a way to override the derserializer with a mixin or something, but nothing that jumps out quickly with a couple of attempts.

@manusa manusa added this to the 5.12.1 milestone Jan 27, 2022
@manusa manusa merged commit 0bc814a into fabric8io:master Feb 1, 2022
manusa pushed a commit that referenced this pull request Feb 4, 2022
* Deserialize WatchEvents using the specific object type

* fallback

* tested

* sonar

(cherry picked from commit 0bc814a)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants