@@ -34,6 +34,7 @@ import (
3434 "google.golang.org/grpc/credentials/insecure"
3535 "google.golang.org/grpc/status"
3636 v1 "k8s.io/api/core/v1"
37+ storagev1 "k8s.io/api/storage/v1"
3738 apierrors "k8s.io/apimachinery/pkg/api/errors"
3839 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3940 "k8s.io/apimachinery/pkg/fields"
@@ -48,6 +49,7 @@ import (
4849 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common"
4950 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common/commonco"
5051 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/logger"
52+ csitypes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/types"
5153 k8s "sigs.k8s.io/vsphere-csi-driver/v3/pkg/kubernetes"
5254 "sigs.k8s.io/vsphere-csi-driver/v3/pkg/syncer/k8scloudoperator"
5355)
@@ -585,10 +587,129 @@ func (c *controller) GetVolumeToHostMapping(ctx context.Context,
585587 return vmMoIDToHostMoID , volumeIDVMMap , nil
586588}
587589
588- // getVolumeIDToVMMap returns the csi list volume response by computing the volumeID to nodeNames map for
589- // fake attached volumes and non-fake attached volumes.
590- func getVolumeIDToVMMap (ctx context.Context , volumeIDs []string , vmMoidToHostMoid ,
591- volumeIDToVMMap map [string ]string ) (* csi.ListVolumesResponse , error ) {
590+ // getPublishedNodesFromVolumeAttachments queries the Kubernetes API for all
591+ // VolumeAttachment objects belonging to the vSphere CSI driver and returns a
592+ // map of CSI volumeHandle → list of node names on which the volume is
593+ // currently published (i.e. VA.Status.Attached == true).
594+ //
595+ // For Pod VM workloads the Kubernetes node that is recorded on the
596+ // VolumeAttachment is the supervisor node (ESXi host), not the individual
597+ // Pod VM. The VA.Status.AttachmentMetadata["vmUUID"] field carries the
598+ // instance UUID of the Pod VM that actually consumed the volume. Both cases
599+ // are captured here so that the ListVolumes response correctly reflects the
600+ // published node for every attach path:
601+ //
602+ // - Regular TKG / supervisor VMs → Spec.NodeName (ESXi host name)
603+ // - Pod VMs → AttachmentMetadata["vmUUID"] if present,
604+ // otherwise falls back to Spec.NodeName
605+ func getPublishedNodesFromVolumeAttachments (
606+ ctx context.Context ,
607+ k8sClient kubernetes.Interface ,
608+ volumeIDs []string ,
609+ ) (map [string ][]string , error ) {
610+ log := logger .GetLogger (ctx )
611+
612+ // Build a set of volumeIDs we care about for O(1) lookup.
613+ volumeIDSet := make (map [string ]struct {}, len (volumeIDs ))
614+ for _ , id := range volumeIDs {
615+ volumeIDSet [id ] = struct {}{}
616+ }
617+
618+ vaList , err := k8sClient .StorageV1 ().VolumeAttachments ().List (ctx , metav1.ListOptions {})
619+ if err != nil {
620+ return nil , fmt .Errorf ("failed to list VolumeAttachments: %v" , err )
621+ }
622+
623+ volumeIDToNodes := make (map [string ][]string )
624+ nodesSeen := make (map [string ]map [string ]struct {}) // volumeID → set of nodes (dedup)
625+
626+ for i := range vaList .Items {
627+ va := & vaList .Items [i ]
628+ if ! isPodVMVolumeAttachment (va ) {
629+ continue
630+ }
631+ if ! va .Status .Attached {
632+ continue
633+ }
634+ if va .Spec .Source .PersistentVolumeName == nil {
635+ continue
636+ }
637+
638+ // Resolve PV name → CSI volumeHandle via the PV object.
639+ pvName := * va .Spec .Source .PersistentVolumeName
640+ pv , err := k8sClient .CoreV1 ().PersistentVolumes ().Get (ctx , pvName , metav1.GetOptions {})
641+ if err != nil {
642+ log .Warnf ("getPublishedNodesFromVolumeAttachments: failed to get PV %q: %v" , pvName , err )
643+ continue
644+ }
645+ if pv .Spec .CSI == nil {
646+ continue
647+ }
648+ volumeHandle := pv .Spec .CSI .VolumeHandle
649+ if _ , ok := volumeIDSet [volumeHandle ]; ! ok {
650+ // Not in the page of volumes we are building a response for.
651+ continue
652+ }
653+
654+ // Determine the node identifier to report.
655+ // For Pod VMs the AttachmentMetadata carries the VM instance UUID;
656+ // use that when present so the caller can correlate to the Pod VM.
657+ // For all other cases report the Kubernetes node name (ESXi host).
658+ nodeName := va .Spec .NodeName
659+ if vmUUID , ok := va .Status .AttachmentMetadata [common .AttributeVmUUID ]; ok && vmUUID != "" {
660+ log .Debugf ("getPublishedNodesFromVolumeAttachments: volume %q is attached to Pod VM UUID %q " +
661+ "on node %q" , volumeHandle , vmUUID , nodeName )
662+ nodeName = vmUUID
663+ }
664+
665+ if _ , exists := nodesSeen [volumeHandle ]; ! exists {
666+ nodesSeen [volumeHandle ] = make (map [string ]struct {})
667+ }
668+ if _ , dup := nodesSeen [volumeHandle ][nodeName ]; ! dup {
669+ nodesSeen [volumeHandle ][nodeName ] = struct {}{}
670+ volumeIDToNodes [volumeHandle ] = append (volumeIDToNodes [volumeHandle ], nodeName )
671+ }
672+ }
673+
674+ log .Debugf ("getPublishedNodesFromVolumeAttachments: volumeID→nodes map: %v" , volumeIDToNodes )
675+ return volumeIDToNodes , nil
676+ }
677+
678+ // isPodVMVolumeAttachment returns true when the VolumeAttachment was created
679+ // by the vSphere CSI driver for a Pod VM workload. A VA is considered to be a
680+ // Pod VM VA when:
681+ // - its attacher field matches the vSphere CSI driver name, AND
682+ // - its Status.AttachmentMetadata contains a "vmUUID" key (populated by the
683+ // driver during ControllerPublishVolume for Pod VMs).
684+ //
685+ // Note: regular supervisor-VM VAs also have the same attacher but will NOT
686+ // have the "vmUUID" metadata key, so this predicate is safe to use without
687+ // additional filtering. The caller must decide how to handle regular VAs.
688+ func isPodVMVolumeAttachment (va * storagev1.VolumeAttachment ) bool {
689+ if va .Spec .Attacher != csitypes .Name {
690+ return false
691+ }
692+ _ , hasPodVMUUID := va .Status .AttachmentMetadata [common .AttributeVmUUID ]
693+ return hasPodVMUUID
694+ }
695+
696+ // getVolumeIDToVMMap returns the csi list volume response by computing the
697+ // volumeID to nodeNames map for fake-attached, regular, and Pod VM volumes.
698+ //
699+ // Pod VM PVCs are attached to ephemeral VMs that run inside the supervisor
700+ // cluster namespace. Their attachment path does not go through the standard
701+ // VM → ESXi host mapping used for regular TKG node VMs. Instead, each Pod VM
702+ // creates a VolumeAttachment whose Status.AttachmentMetadata["vmUUID"] holds
703+ // the instance UUID of the Pod VM. This function queries all VolumeAttachment
704+ // objects to discover Pod VM published nodes and merges them into the response
705+ // alongside the host-based published nodes for regular volumes.
706+ func getVolumeIDToVMMap (
707+ ctx context.Context ,
708+ k8sClient kubernetes.Interface ,
709+ volumeIDs []string ,
710+ vmMoidToHostMoid map [string ]string ,
711+ volumeIDToVMMap map [string ]string ,
712+ ) (* csi.ListVolumesResponse , error ) {
592713 log := logger .GetLogger (ctx )
593714 response := & csi.ListVolumesResponse {}
594715
@@ -600,7 +721,7 @@ func getVolumeIDToVMMap(ctx context.Context, volumeIDs []string, vmMoidToHostMoi
600721 }
601722 }
602723
603- // Process fake attached volumes
724+ // ── 1. Fake- attached volumes ──────────────────────────────────────────────
604725 log .Debugf ("Fake attached volumes %v" , fakeAttachedVolumes )
605726 volumeIDToNodesMap := commonco .ContainerOrchestratorUtility .GetNodesForVolumes (ctx , fakeAttachedVolumes )
606727 for volumeID , publishedNodeIDs := range volumeIDToNodesMap {
@@ -617,6 +738,42 @@ func getVolumeIDToVMMap(ctx context.Context, volumeIDs []string, vmMoidToHostMoi
617738 response .Entries = append (response .Entries , entry )
618739 }
619740
741+ // ── 2. Pod VM volumes (VolumeAttachment-based lookup) ────────────────────
742+ // Pod VMs are ephemeral VMs running inside supervisor namespaces. Their
743+ // disks do not appear in the host-VM hardware scan performed by
744+ // GetVolumeToHostMapping, so we must derive their published nodes directly
745+ // from the VolumeAttachment status.
746+ podVMPublishedNodes , err := getPublishedNodesFromVolumeAttachments (ctx , k8sClient , volumeIDs )
747+ if err != nil {
748+ // Non-fatal: log and continue. The regular VM path below will still
749+ // populate published nodes for non-Pod VM volumes.
750+ log .Warnf ("getVolumeIDToVMMap: failed to get Pod VM published nodes from VolumeAttachments: %v" , err )
751+ }
752+
753+ // Track which volumes have already been added via the Pod VM path so we
754+ // don't double-count them in the regular VM section below.
755+ podVMHandled := make (map [string ]struct {}, len (podVMPublishedNodes ))
756+ for volumeID , nodeIDs := range podVMPublishedNodes {
757+ podVMHandled [volumeID ] = struct {}{}
758+ // Skip volumes that are already represented as fake-attached above.
759+ if _ , isFake := allFakeAttachMarkedVolumes [volumeID ]; isFake {
760+ continue
761+ }
762+ volume := & csi.Volume {
763+ VolumeId : volumeID ,
764+ }
765+ volumeStatus := & csi.ListVolumesResponse_VolumeStatus {
766+ PublishedNodeIds : nodeIDs ,
767+ }
768+ entry := & csi.ListVolumesResponse_Entry {
769+ Volume : volume ,
770+ Status : volumeStatus ,
771+ }
772+ log .Debugf ("getVolumeIDToVMMap: Pod VM volume %q published on nodes %v" , volumeID , nodeIDs )
773+ response .Entries = append (response .Entries , entry )
774+ }
775+
776+ // ── 3. Regular VM volumes (ESXi host-based lookup) ───────────────────────
620777 hostNames := commonco .ContainerOrchestratorUtility .GetNodeIDtoNameMap (ctx )
621778 if len (hostNames ) == 0 {
622779 log .Errorf ("no hostnames found in the NodeIDtoName map" )
@@ -625,13 +782,14 @@ func getVolumeIDToVMMap(ctx context.Context, volumeIDs []string, vmMoidToHostMoi
625782
626783 for volumeID , VMMoID := range volumeIDToVMMap {
627784 isFakeAttached , exists := allFakeAttachMarkedVolumes [volumeID ]
628- // If we do not find this entry in the input list obtained from CNS
629- //, then we do not bother adding it to the result since, CNS is not aware
630- // of this volume. Also, if it is fake attached volume we have handled it
631- // above so we will not add it to the response here.
785+ // Skip volumes CNS is not aware of, or already handled via fake-attach
786+ // or Pod VM paths.
632787 if ! exists || isFakeAttached {
633788 continue
634789 }
790+ if _ , handledByPodVM := podVMHandled [volumeID ]; handledByPodVM {
791+ continue
792+ }
635793
636794 hostMoID , ok := vmMoidToHostMoid [VMMoID ]
637795 if ! ok {
@@ -642,8 +800,7 @@ func getVolumeIDToVMMap(ctx context.Context, volumeIDs []string, vmMoidToHostMoi
642800 if ! ok {
643801 continue
644802 }
645- publishedNodeIDs := make ([]string , 0 )
646- publishedNodeIDs = append (publishedNodeIDs , hostName )
803+ publishedNodeIDs := []string {hostName }
647804 volume := & csi.Volume {
648805 VolumeId : volumeID ,
649806 }
0 commit comments