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

Bump kubernetes-client-bom from 6.6.2 to 6.7.1 #33767

Merged
merged 3 commits into from
Jun 12, 2023

Conversation

manusa
Copy link
Contributor

@manusa manusa commented Jun 1, 2023

Kubernetes Client 6.7.0 was just released: https://github.com/fabric8io/kubernetes-client/releases/tag/v6.7.0

Kubernetes Client 6.7.1 was just released: https://github.com/fabric8io/kubernetes-client/releases/tag/v6.7.1

Just speeding up the dependabot process to see if CI reports any issue.

/cc @metacosm

@quarkus-bot
Copy link

quarkus-bot bot commented Jun 1, 2023

Thanks for your pull request!

The title of your pull request does not follow our editorial rules. Could you have a look?

  • title should preferably start with an uppercase character (if it makes sense!)

This message is automatically generated by a bot.

@manusa manusa changed the title deps: Bump kubernetes-client-bom from 6.6.2 to 6.7.0 Bump kubernetes-client-bom from 6.6.2 to 6.7.0 Jun 1, 2023
@metacosm
Copy link
Contributor

metacosm commented Jun 1, 2023

Should be OK on our side. See new comment.

@quarkus-bot

This comment has been minimized.

@geoand
Copy link
Contributor

geoand commented Jun 1, 2023

The native test failure looks very suspicious

@manusa
Copy link
Contributor Author

manusa commented Jun 1, 2023

I'll look into it

@metacosm
Copy link
Contributor

metacosm commented Jun 1, 2023

Note that we've been running native tests with snapshots of 6.7 without issue so the problem might come from the mock server. To be honest, I'm not even sure what the failing tests are testing 😅 since they seem to only check that the mock server is returning the answer it is set to return in the setup method, they don't appear to use the client at all. So, at least without further digging, these tests seem useless to me for the purpose of testing the client.
That said, the fact that they're failing is indeed suspicious.

@manusa
Copy link
Contributor Author

manusa commented Jun 2, 2023

That's a good hint (the fact that the native tests have been run with the SNAPSHOTs).

However, this is the detailed log message (irrelevant parts omitted) of the test failure:

bb0e-b8ae5a0ddb40-1: org.jboss.resteasy.spi.UnhandledException: io.fabric8.kubernetes.client.KubernetesClientException: io.fabric8.kubernetes.api.model.KubernetesResource: Provider io.fabric8.kubernetes.api.model.gatewayapi.v1alpha2.HTTPRouteList not found
...
2023-06-01T13:43:28.5431445Z Caused by: io.fabric8.kubernetes.client.KubernetesClientException: io.fabric8.kubernetes.api.model.KubernetesResource: Provider io.fabric8.kubernetes.api.model.gatewayapi.v1alpha2.HTTPRouteList not found
2023-06-01T13:43:28.5432300Z 	at io.fabric8.kubernetes.client.dsl.internal.OperationSupport.waitForResult(OperationSupport.java:520)
...
2023-06-01T13:43:28.5436671Z 	at io.quarkus.it.kubernetes.client.Pods.deleteFirst(Pods.java:41)
2023-06-01T13:43:28.5444828Z Caused by: java.util.ServiceConfigurationError: io.fabric8.kubernetes.api.model.KubernetesResource: Provider io.fabric8.kubernetes.api.model.gatewayapi.v1alpha2.HTTPRouteList not found
2023-06-01T13:43:28.5445496Z 	at [email protected]/java.util.ServiceLoader.fail(ServiceLoader.java:593)
...
2023-06-01T13:43:28.5448588Z 	at io.fabric8.kubernetes.internal.KubernetesDeserializer$Mapping.registerClasses(KubernetesDeserializer.java:208)
2023-06-01T13:43:28.5449290Z 	at io.fabric8.kubernetes.internal.KubernetesDeserializer$Mapping.registerClassesFromClassLoaders(KubernetesDeserializer.java:200)
2023-06-01T13:43:28.5449931Z 	at io.fabric8.kubernetes.internal.KubernetesDeserializer.<init>(KubernetesDeserializer.java:88)
2023-06-01T13:43:28.5450643Z 	at io.fabric8.kubernetes.client.utils.KubernetesSerialization.getKubernetesDeserializer(KubernetesSerialization.java:147)
2023-06-01T13:43:28.5451382Z 	at io.fabric8.kubernetes.client.utils.KubernetesSerialization.access$000(KubernetesSerialization.java:64)
2023-06-01T13:43:28.5452193Z 	at io.fabric8.kubernetes.client.utils.KubernetesSerialization$1.deserializerInstance(KubernetesSerialization.java:99)

I'm mostly sure it has to do with the more elaborate changed we did to to serialization in this release fabric8io/kubernetes-client#4662

However, Chris' comment makes me believe that maybe is an issue while registering the SPI modules 🤷

@manusa manusa marked this pull request as draft June 2, 2023 04:08
@manusa
Copy link
Contributor Author

manusa commented Jun 2, 2023

So the deserializer is now lazy initialized at runtime when used:

https://github.com/fabric8io/kubernetes-client/blob/296aafd0fba5ec40b9bfb450b6020107a028a38e/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java#L144-L150

Also the deserializer is no longer static but an instance (first step to allow having multiple serialization options for the same application):

fabric8io/kubernetes-client@6d7db80

Probably this is the cause of the issue.

@manusa
Copy link
Contributor Author

manusa commented Jun 2, 2023

Not sure if the new KubernetesSerialization needs to be registered somewhere around here:

reflectiveClasses.produce(
ReflectiveClassBuildItem.builder(KubernetesClientImpl.class, DefaultKubernetesClient.class, VersionInfo.class)
.methods().fields().build());
reflectiveClasses.produce(ReflectiveClassBuildItem
.builder(AnyType.class, IntOrString.class, KubernetesDeserializer.class).methods().build());

@manusa
Copy link
Contributor Author

manusa commented Jun 2, 2023

The native binary built for the integration test fails as soon as it tries to deserialize something (with the same error -HTTPRouteList-. If ran from the command-line, it will fail as soon as it tries to read the local .kube/config file.

$  ./target/quarkus-integration-test-kubernetes-client-999-SNAPSHOT-runner 
Jun 02, 2023 6:44:24 AM io.quarkus.runtime.ApplicationLifecycleManager run
ERROR: Failed to start application (with profile [prod])                          quarkus-integration-test-kubernetes-client-999-SNAPSHOT-runner*                 
java.util.ServiceConfigurationError: io.fabric8.kubernetes.api.model.KubernetesResource: Provider io.fabric8.kubernetes.api.model.gatewayapi.v1alpha2.HTTPRouteList not found
        at [email protected]/java.util.ServiceLoader.fail(ServiceLoader.java:593)
...
        at io.fabric8.kubernetes.internal.KubernetesDeserializer$Mapping.registerClasses(KubernetesDeserializer.java:208)
        at io.fabric8.kubernetes.internal.KubernetesDeserializer$Mapping.registerClassesFromClassLoaders(KubernetesDeserializer.java:200)
        at io.fabric8.kubernetes.internal.KubernetesDeserializer.<init>(KubernetesDeserializer.java:88)
        at io.fabric8.kubernetes.client.utils.KubernetesSerialization.getKubernetesDeserializer(KubernetesSerialization.java:147)
...
        at io.fabric8.kubernetes.client.Config.loadFromKubeconfig(Config.java:704)
        at io.fabric8.kubernetes.client.Config.tryKubeConfig(Config.java:668)
        at io.fabric8.kubernetes.client.Config.autoConfigure(Config.java:286)
        at io.fabric8.kubernetes.client.Config.autoConfigure(Config.java:282)
        at io.quarkus.kubernetes.client.runtime.KubernetesClientUtils.createConfig(KubernetesClientUtils.java:18)
...

@geoand
Copy link
Contributor

geoand commented Jun 2, 2023

In general, anything that uses the Java ServiceLoader, needs to be explicitly registered for it to work in native mode.

@manusa
Copy link
Contributor Author

manusa commented Jun 2, 2023

Yes, that was taken care of some time ago.
It seems to be a problem with the service loader. I'll try to implement a fix in an hour or so. Will let you know how it works out. If it doesn't I may need some help to continue.

@manusa
Copy link
Contributor Author

manusa commented Jun 2, 2023

So providing a static KubernetesSerialization instance does the trick and allows the class loaders ro detect the SPI provided KubernetesResource classes.

We should probably come up with a better and more elegant solution to deal with the class loaders. However, I think that this will require additional work both in the client and in Quarkus.

Also, the current implementation has a small workaround to be able to register the KubernetesResources when the static variable is initialized by invoking SERIALIZATION.getRegisteredKubernetesResource("", "");.

I created some changes in the Client (fabric8io/kubernetes-client#5200) that when released will allow for something nicer. However, if everyone agrees I think we can proceed with the current changes and create a follow up issue to implement KubernetesSerialization class loading from Quarkus' KubernetesClientUtils after we release the next Kubernetes Client version (I would take care of fixing that one) ---> not urgent.

/cc @shawkins

@manusa manusa requested a review from geoand June 2, 2023 11:51
@quarkus-bot

This comment has been minimized.

import io.quarkus.runtime.TlsConfig;

public class KubernetesClientUtils {

private static final String PREFIX = "quarkus.kubernetes-client.";
private static final KubernetesSerialization SERIALIZATION = new KubernetesSerialization(new ObjectMapper(), true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we use the Quarkus ObjectMapper here, in case it has been customised by users?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an important point.

I'd first ask how were users customizing the Fabric8's serialization purposes ObjectMapper before.
If mutating Serialization.jsonMapper() is the answer to the previous question and this was an extended practice, then we should definitely provide a way to customize this mapper. With the current changes, this will no longer be possible.

The next point would be to know if we want to share a same ObjectMapper used application-wise with the client or have an exclusive one for it. Some mapper configurations might not be ideal for the client or even break it.

Copy link
Contributor

Choose a reason for hiding this comment

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

IMHO, it should not use the user customizable mapper, because the client itself knows everything about the model serialization/deserialization and should not be affected by the user's configuration

Copy link
Contributor

Choose a reason for hiding this comment

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

@geoand this isn't quite true, for example, if people want to use Kotlin or other libs in conjunction to the Fabric8 client, they would need to customise the mapper to make it work correctly with the client.

Copy link
Contributor

Choose a reason for hiding this comment

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

If that's the case, I think it would make more sense to have a way to configure this mapper specifically

Copy link
Contributor

@metacosm metacosm Jun 6, 2023

Choose a reason for hiding this comment

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

As far as I'm aware, without being a kotlin expert, you cannot use the fabric8 client from kotlin without customizing the fabric8 mapper. Similarly, as far as I'm aware, you cannot have two different mappers take part into the serialisation of one single object. So I would very much like to hear your actual solutions to both of these issues apart from saying you shouldn't customise the mapper because that doesn't really get our users anywhere, and by users, I mean Quarkus users of the Fabric8 client, not JOSDK users.

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 created a bean and qualifier to be able to inject the KubernetesClient-specific ObjectMapper for further modifications such as the Kotlin use case. (see @KubernetesClientObjectMapper)

Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't it make more sense to implement an approach similar to what I did in QOSDK, i.e. using the customizer approach, instead of this approach which would require people to create a whole new ObjectMapper (at least, if I understand things properly)? That seems like exactly the kind of thing we would rather avoid because that would require people to know how the original mapper is configured so that they don't break the client's functionality. We would rather want people to add their configuration on top of what is already configured instead of doing it from scratch.

Also, with this approach, shouldn't the producing method of KubernetesClientObjectMapperProducer be marked as DefaultBean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just deleted the previous reply (when I wrote it, I was confused and mixed two of Chris' comments)

The ObjectMapper that we pass into the KubernetesSerialization will get customized by the Fabric8 Client (at a later point).

With the latest changes, users are able to pass their own ObjectMapper or a new ObjectMapper will be created for them. There's no reason why users should do this besides enabling the Kotlin stuff. I think it's now clearer that this Mapper has no customization whatsoever until it has been injected into the KubernetesSerializationProducer and a new KubernetesSerialization is created.

Copy link
Contributor

@metacosm metacosm Jun 7, 2023

Choose a reason for hiding this comment

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

As explained above customisation could occur for a variety of reasons. I still think that users should customise the instance rather than provide their own. We might not use any customisation at this point, but should that change, users would need to use the same customisation and add theirs on top so I really believe customizing the existing instance is a better choice than injecting a new one. This would also allows us to control at the customisation point that the customisations are indeed compatible with the needs of the client.

This would also fit with what Quarkus is doing with the ObjectMapperCustomizer feature.

@manusa manusa force-pushed the deps/kubernetes-client branch from e7ad0b7 to f43669e Compare June 7, 2023 09:20
@manusa manusa changed the title Bump kubernetes-client-bom from 6.6.2 to 6.7.0 Bump kubernetes-client-bom from 6.6.2 to 6.7.1 Jun 7, 2023
import io.quarkus.runtime.TlsConfig;

public class KubernetesClientUtils {

private static final String PREFIX = "quarkus.kubernetes-client.";
private static final ObjectMapper KUBERNETES_CLIENT_MAPPER = new ObjectMapper();
Copy link
Contributor

Choose a reason for hiding this comment

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

I very much want to avoid this, but I would need to time to look into alternatives

Copy link
Contributor

Choose a reason for hiding this comment

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

So in order for me to understand: before this change, was the k8s client in Quarkus using it's own mapper or not?

Copy link
Contributor

@geoand geoand Jun 7, 2023

Choose a reason for hiding this comment

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

Looking at this more, we probably can't avoid is done here. What we do want to avoid however is using lambdas in runtime code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So in order for me to understand: before this change, was the k8s client in Quarkus using it's own mapper or not?

The Kubernetes Client was using a static serialization utils class. Everything was static.

We're doing some changes to make the serialization part instantiatable, so you can have multiple clients with multiple serialization configurations.

Seems like the move to instances is not playing nice with SPI, native, and these processors.

Copy link
Contributor

Choose a reason for hiding this comment

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

The Kubernetes Client was using a static serialization utils class. Everything was static.

Good to know. Just to make sure there is no misunderstanding: The old code was instantiating an ObjectMapper, correct?

Copy link
Contributor

Choose a reason for hiding this comment

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

Gotcha thanks. In that case, I am pretty sure that this general approach augmented with build time discovery of KubernetesResource will be fine.
I'll have a look at the latter tomorrow

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand this, actually. If we make it possible for users to inject a mapper, shouldn't that mapper be used here instead of a static version that won't be configured the same way?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not application code, but build time code. The mapper is not really the problem, but the registration of the KubernetesResource classes is, since doing this at runtime is not working.

Copy link
Contributor

Choose a reason for hiding this comment

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

This code is in the runtime module, therefore accessible to users and I do know users that use this class to create instances of the client so I don't think that this is strictly build time code, even if we'd like to think it is…

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At the current state, there are no notable differences with what was done before bumping. The logic that was run upstream is now located here. In fact, just running the scanKubernetesResources() method from the static block should suffice to fix the native issue.

// since those classes won't be available and won't be registered in the KubernetesSerialization instance
private static final Set<Class<? extends KubernetesResource>> KUBERNETES_RESOURCE_CLASES = scanKubernetesResources();
private static final KubernetesSerialization SERIALIZATION = new KubernetesSerialization(KUBERNETES_CLIENT_MAPPER, false);
static {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should probably add all initialization in this block, otherwise the code is hard to follow

Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above: shouldn't this use the injectable version instead of a static version?

@manusa
Copy link
Contributor Author

manusa commented Jun 10, 2023

Caused by: java.lang.ClassNotFoundException: io.fabric8.openshift.api.model.hive.v1.ClusterImageSet
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:516)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:466)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:516)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:466)
        at java.base/java.lang.Class.forName0(Native Method)
        at java.base/java.lang.Class.forName(Class.java:467)
        at io.quarkus.deployment.steps.KubernetesResourceBuildStep$kubernetesResourceClasses1885090519.deploy_0(Unknown Source)
        at io.quarkus.deployment.steps.KubernetesResourceBuildStep$kubernetesResourceClasses1885090519.deploy(Unknown Source)
        ... 50 more

It does seem that the TCCL is unable to find the SPI provided classes that were recorded during build time at the static init phase.

@manusa manusa force-pushed the deps/kubernetes-client branch from b27efeb to 21982eb Compare June 10, 2023 07:17
@manusa
Copy link
Contributor Author

manusa commented Jun 10, 2023

So it seems that I did actually mischose the package and module location. Moved some things around and everything seems to be working locally now.

@quarkus-bot

This comment has been minimized.

@manusa
Copy link
Contributor Author

manusa commented Jun 12, 2023

OK, so for the next set of failures integration-tests/kubernetes (used for build time deployment, and so on), it looks like the classes injected (that were discovered by the TCCL) are not available:

Caused by: java.lang.ClassNotFoundException: io.fabric8.openshift.api.model.User
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
	at io.quarkus.bootstrap.runner.RunnerClassLoader.loadClass(RunnerClassLoader.java:115)
	at io.quarkus.bootstrap.runner.RunnerClassLoader.loadClass(RunnerClassLoader.java:65)
	at java.base/java.lang.Class.forName0(Native Method)

So, when preparing the list of classes, we should filter those that are provided by the deployment extension (only needed for deployment purposes) from those that are production classes (needed by the application at runtime). Not sure how to do this though. Any ideas?

@geoand
Copy link
Contributor

geoand commented Jun 12, 2023

I would suggest the following:

Instead of using

@Recorder
public class KubernetesSerializationRecorder {

    public RuntimeValue<Class<? extends KubernetesResource>[]> initKubernetesResources(
            Class<? extends KubernetesResource>[] resources) {
        return new RuntimeValue<>(resources);
    }
}

pass in an array of String and then use Class.forName(className, false, Thread.currentThread().getContextClassLoader() in the recorder body

@manusa
Copy link
Contributor Author

manusa commented Jun 12, 2023

That's what I was more or less thinking of, but it felt a little bit dirty. Knowing that you share the same approach I'm more comfortable with it 😅.
Will try that and push the changes if it works
Thx!

@geoand
Copy link
Contributor

geoand commented Jun 12, 2023

It's pretty much what Quarkus does under the covers anyway, but I don't remember which ClassLoader it uses, so we can try and be explicit

@manusa
Copy link
Contributor Author

manusa commented Jun 12, 2023

but I don't remember which ClassLoader it uses, so we can try and be explicit

It depends on the phase.

For the scanKubernetesResourceClasses step it's the Augmentation Class Loader: PROD.

For the kubernetesResourceClasses step first invocation is the Deployment Class Loader: PROD. And for the second it's the RunnerClassLoader. So I'm not sure we can actually be explicit about it.

@geoand
Copy link
Contributor

geoand commented Jun 12, 2023

I was referring to the CL used under the covers when "recording" classes.

@manusa
Copy link
Contributor Author

manusa commented Jun 12, 2023

Unfortunately, the workaround is not working since the recorded bytecode is played as it is and the exception is still thrown for the second invocation.

@geoand
Copy link
Contributor

geoand commented Jun 12, 2023

second invocation

Sorry, I am not following this.

@manusa manusa force-pushed the deps/kubernetes-client branch from 21982eb to a8b005f Compare June 12, 2023 08:23
@geoand
Copy link
Contributor

geoand commented Jun 12, 2023

Now I see what you meant earlier today.

So, when preparing the list of classes, we should filter those that are provided by the deployment extension (only needed for deployment purposes) from those that are production classes (needed by the application at runtime). Not sure how to do this though. Any ideas?

Yes, there is a way and we should indeed use it in order to reduce the recorded bytecode size.

QuarkusClassLoader#isClassPresentAtRuntime

https://i.imgflip.com/4pw07x.jpg

@manusa manusa force-pushed the deps/kubernetes-client branch from a8b005f to fea1aee Compare June 12, 2023 09:43
Copy link
Contributor

@geoand geoand left a comment

Choose a reason for hiding this comment

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

Thanks a lot!

@quarkus-bot
Copy link

quarkus-bot bot commented Jun 12, 2023

Failing Jobs - Building fea1aee

Status Name Step Failures Logs Raw logs
✔️ JVM Tests - JDK 11
✔️ JVM Tests - JDK 17
JVM Tests - JDK 17 Windows Build ⚠️ Check → Logs Raw logs
✔️ JVM Tests - JDK 19

@geoand geoand merged commit 35cb41a into quarkusio:main Jun 12, 2023
@quarkus-bot quarkus-bot bot added this to the 3.2 - main milestone Jun 12, 2023
@manusa manusa deleted the deps/kubernetes-client branch June 12, 2023 17:42
fedinskiy added a commit to fedinskiy/quarkus-test-framework that referenced this pull request Jun 14, 2023
Fabric8 team removed[1][2] constructors, which we were using to create Openshift client
Switched to other methods.
This change was brought to Quarkus with this update[3]

[1] fabric8io/kubernetes-client@6d7db80#diff-92a1266d167b46838301a64f043de2895376ebf7ec810e7769d641d2fed6f511L227-L246
[2] fabric8io/kubernetes-client#4662
[3] quarkusio/quarkus#33767
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants