Skip to content

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

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

Open
kbatalin opened this issue Apr 9, 2025 · 4 comments

Comments

@kbatalin
Copy link

kbatalin commented Apr 9, 2025

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

@brendandburns
Copy link
Contributor

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.

@kbatalin
Copy link
Author

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

@brendandburns
Copy link
Contributor

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

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

No branches or pull requests

2 participants