java icon indicating copy to clipboard operation
java copied to clipboard

Informer: Initial list items missing kind/apiVersion compared to watch events

Open kbatalin opened this issue 7 months ago • 4 comments
trafficstars

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:

  1. In your informer callback handler, log or inspect the received Pod objects.
  2. Observe that during the initial list operation, the nested Pod objects have null values for kind and apiVersion.
  3. 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

kbatalin avatar Apr 09 '25 16:04 kbatalin

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 avatar Apr 09 '25 19:04 brendandburns

@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:

  1. 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
  1. 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
...

kbatalin avatar Apr 10 '25 09:04 kbatalin

Hrm, thanks for drilling into the reproduction, I'll see if I can figure out what is going wrong in the parsing then.

brendandburns avatar Apr 10 '25 21:04 brendandburns

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/stale is applied
  • After 30d of inactivity since lifecycle/stale was applied, lifecycle/rotten is applied
  • After 30d of inactivity since lifecycle/rotten was 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

k8s-triage-robot avatar Jul 17 '25 22:07 k8s-triage-robot

/remove-lifecycle stale

kbatalin avatar Jul 18 '25 07:07 kbatalin

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/stale is applied
  • After 30d of inactivity since lifecycle/stale was applied, lifecycle/rotten is applied
  • After 30d of inactivity since lifecycle/rotten was 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

k8s-triage-robot avatar Oct 16 '25 08:10 k8s-triage-robot

/remove-lifecycle stale

kbatalin avatar Oct 16 '25 08:10 kbatalin