java
java copied to clipboard
Informer: Initial list items missing kind/apiVersion compared to watch events
Describe the bug
When using informers, the initial list operation returns a V1PodList where the nested Pod objects do not have the kind and apiVersion fields populated (they are null). However, when the informer subsequently receives watch events, the individual Pod objects contain the expected kind (e.g. "Pod") and apiVersion (e.g. "v1"). This inconsistency in the returned data can lead to confusion and issues for applications that depend on these fields being consistently set.
Client Version
23.0.0
Kubernetes Version
1.30
Java Version Java 17
To Reproduce Steps to reproduce the behavior:
- In your informer callback handler, log or inspect the received Pod objects.
- Observe that during the initial list operation, the nested Pod objects have null values for kind and apiVersion.
- Later, when the watch events are received, the same fields appear correctly populated.
Expected behavior It is expected that all Pod objects processed by the informer should consistently contain the kind and apiVersion fields.
Additional context This behavior leads to an inconsistency within the same informer callback - first receiving Pods with incomplete metadata followed by Pods with the correct metadata. This makes it harder to rely on these fields for subsequent processing, and it is unclear whether this is an intentional design choice for efficiency or an oversight in the client’s deserialization process. It would be beneficial if the client could ensure that the nested objects always include the metadata
Unfortunately, I believe that this is the expected behavior based on the Kubernetes API Server responses.
The initial list is based on a list operation (similar to kubectl get pods) if you look at the JSON returned from that request (kubectl get pods -o json) you will see that group and kind are not populated.
But the remaining watch data is populated in the individual resources.
We might be able to hack this together via the ModelMapper class if you want to try that we can review PRs.
@brendandburns Thanks for the prompt response
The initial list is based on a list operation (similar to kubectl get pods) if you look at the JSON returned from that request (kubectl get pods -o json) you will see that group and kind are not populated.
Looks like they are populated:
- Create a cluster with pods:
k3d cluster create mycluster
kubectl create deployment my-deployment-1 --image=nginx
kubectl create deployment my-deployment-2 --image=nginx
kubectl create deployment my-deployment-3 --image=nginx
- kubectl get pods:
kubectl get pods -o json | jq '.items[] | {apiVersion, kind}'
{
"apiVersion": "v1",
"kind": "Pod"
}
{
"apiVersion": "v1",
"kind": "Pod"
}
{
"apiVersion": "v1",
"kind": "Pod"
}
And the minimal working code to reproduce the scenario:
public class TestInformers {
public static void main(String[] args) throws InterruptedException, IOException {
ApiClient apiClient = Config.defaultClient();
apiClient.setReadTimeout(0);
SharedInformerFactory factory = new SharedInformerFactory(apiClient);
CoreV1Api coreV1Api = new CoreV1Api(apiClient);
SharedIndexInformer<V1Pod> podInformer =
factory.sharedIndexInformerFor(
(CallGeneratorParams params) -> {
return coreV1Api
.listPodForAllNamespaces()
.resourceVersion(params.resourceVersion)
.watch(params.watch)
.timeoutSeconds(params.timeoutSeconds)
.buildCall(null);
},
V1Pod.class,
V1PodList.class);
podInformer.addEventHandler(
new ResourceEventHandler<V1Pod>() {
@Override
public void onAdd(V1Pod pod) {
if (pod.getApiVersion() == null || pod.getKind() == null) {
System.out.printf("Pod %s has no apiVersion or kind%n", pod.getMetadata().getName());
} else {
System.out.printf(
"Pod %s added with apiVersion %s and kind %s%n",
pod.getMetadata().getName(), pod.getApiVersion(), pod.getKind());
}
}
@Override
public void onUpdate(V1Pod oldPod, V1Pod newPod) {
}
@Override
public void onDelete(V1Pod pod, boolean deletedFinalStateUnknown) {
}
});
factory.startAllRegisteredInformers();
Thread.sleep(10_000);
factory.stopAllRegisteredInformers();
}
}
Output:
Pod my-deployment-1-544fdd4b9f-p98kl has no apiVersion or kind
Pod my-deployment-2-6d698c8d76-g75d6 has no apiVersion or kind
Pod my-deployment-3-577cb8dc7-tmt65 has no apiVersion or kind
...
Hrm, thanks for drilling into the reproduction, I'll see if I can figure out what is going wrong in the parsing then.
The Kubernetes project currently lacks enough contributors to adequately respond to all issues.
This bot triages un-triaged issues according to the following rules:
- After 90d of inactivity,
lifecycle/staleis applied - After 30d of inactivity since
lifecycle/stalewas applied,lifecycle/rottenis applied - After 30d of inactivity since
lifecycle/rottenwas applied, the issue is closed
You can:
- Mark this issue as fresh with
/remove-lifecycle stale - Close this issue with
/close - Offer to help out with Issue Triage
Please send feedback to sig-contributor-experience at kubernetes/community.
/lifecycle stale
/remove-lifecycle stale
The Kubernetes project currently lacks enough contributors to adequately respond to all issues.
This bot triages un-triaged issues according to the following rules:
- After 90d of inactivity,
lifecycle/staleis applied - After 30d of inactivity since
lifecycle/stalewas applied,lifecycle/rottenis applied - After 30d of inactivity since
lifecycle/rottenwas applied, the issue is closed
You can:
- Mark this issue as fresh with
/remove-lifecycle stale - Close this issue with
/close - Offer to help out with Issue Triage
Please send feedback to sig-contributor-experience at kubernetes/community.
/lifecycle stale
/remove-lifecycle stale