@@ -27,6 +27,8 @@ import (
2727 "time"
2828
2929 log "github.com/sirupsen/logrus"
30+ corev1 "k8s.io/api/core/v1"
31+ apierrors "k8s.io/apimachinery/pkg/api/errors"
3032 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3133 "k8s.io/apimachinery/pkg/types"
3234 "k8s.io/apimachinery/pkg/util/wait"
@@ -42,6 +44,7 @@ const (
4244 dcgmExporterDeployLabel = "nvidia.com/gpu.deploy.dcgm-exporter"
4345 dcgmDeployLabel = "nvidia.com/gpu.deploy.dcgm"
4446 nvsmDeployLabel = "nvidia.com/gpu.deploy.nvsm"
47+ gpuClientDeployLabel = "nvidia.com/gpu.deploy.client"
4548
4649 migStateSuccess = "success"
4750 migStateFailed = "failed"
@@ -93,6 +96,7 @@ type Reconfigure struct {
9396 dcgmExporterDeployed string
9497 dcgmDeployed string
9598 nvsmDeployed string
99+ gpuClientDeployed string
96100 currentState string
97101 migModeChangeRequired bool
98102 stoppedServices []string
@@ -255,13 +259,16 @@ func (r *Reconfigure) getCurrentNodeLabels() error {
255259 r .dcgmExporterDeployed = labels [dcgmExporterDeployLabel ]
256260 r .dcgmDeployed = labels [dcgmDeployLabel ]
257261 r .nvsmDeployed = labels [nvsmDeployLabel ]
262+ r .gpuClientDeployed = labels [gpuClientDeployLabel ]
258263 r .currentState = labels [migConfigStateLabel ]
259264
260265 log .Infof ("Current value of '%s=%s'\n " , devicePluginDeployLabel , r .pluginDeployed )
261266 log .Infof ("Current value of '%s=%s'\n " , gpuFeatureDiscoveryDeployLabel , r .gfdDeployed )
262267 log .Infof ("Current value of '%s=%s'\n " , dcgmExporterDeployLabel , r .dcgmExporterDeployed )
263268 log .Infof ("Current value of '%s=%s'\n " , dcgmDeployLabel , r .dcgmDeployed )
264269 log .Infof ("Current value of '%s=%s'\n " , nvsmDeployLabel , r .nvsmDeployed )
270+ log .Infof ("Current value of '%s=%s'\n " , gpuClientDeployLabel , r .gpuClientDeployed )
271+
265272 log .Infof ("Current value of '%s=%s'\n " , migConfigStateLabel , r .currentState )
266273
267274 return nil
@@ -370,6 +377,7 @@ func (r *Reconfigure) shutdownKubernetesGPUClients() error {
370377 dcgmExporterDeployLabel : r .maybeSetPaused (r .dcgmExporterDeployed ),
371378 dcgmDeployLabel : r .maybeSetPaused (r .dcgmDeployed ),
372379 nvsmDeployLabel : r .maybeSetPaused (r .nvsmDeployed ),
380+ gpuClientDeployLabel : r .maybeSetPaused (r .gpuClientDeployed ),
373381 }
374382
375383 return r .setNodeLabels (labels )
@@ -399,6 +407,11 @@ func (r *Reconfigure) waitForPodsToBeDeleted() error {
399407 return fmt .Errorf ("dcgm pod did not shutdown: %w" , err )
400408 }
401409
410+ log .Infof ("Waiting for any pods with parent nodeSelector key %s to shutdown" , gpuClientDeployLabel )
411+ if err := r .waitForPodsWithParentNodeSelector (gpuClientDeployLabel , timeout ); err != nil {
412+ return fmt .Errorf ("third-party gpu client pods did not shutdown: %w" , err )
413+ }
414+
402415 log .Info ("Removing the cuda-validator pod" )
403416 if err := r .deletePod ("app=nvidia-cuda-validator" ); err != nil {
404417 return fmt .Errorf ("failed to delete cuda-validator pod: %w" , err )
@@ -507,6 +520,7 @@ func (r *Reconfigure) restartKubernetesGPUClients() error {
507520 dcgmExporterDeployLabel : r .maybeSetTrue (r .dcgmExporterDeployed ),
508521 dcgmDeployLabel : r .maybeSetTrue (r .dcgmDeployed ),
509522 nvsmDeployLabel : r .maybeSetTrue (r .nvsmDeployed ),
523+ gpuClientDeployLabel : r .maybeSetTrue (r .gpuClientDeployed ),
510524 }
511525
512526 return r .setNodeLabels (labels )
@@ -538,6 +552,7 @@ func (r *Reconfigure) setState(state string) error {
538552 dcgmExporterDeployLabel : r .maybeSetTrue (r .dcgmExporterDeployed ),
539553 dcgmDeployLabel : r .maybeSetTrue (r .dcgmDeployed ),
540554 nvsmDeployLabel : r .maybeSetTrue (r .nvsmDeployed ),
555+ gpuClientDeployLabel : r .maybeSetTrue (r .gpuClientDeployed ),
541556 }
542557
543558 if err := r .setNodeLabels (labels ); err != nil {
@@ -621,6 +636,69 @@ func (r *Reconfigure) deletePod(labelSelector string) error {
621636 return nil
622637}
623638
639+ // waitForPodsWithParentNodeSelector waits for all pods on the given node whose parent controller
640+ // has the specified key in its nodeSelector to terminate.
641+ //
642+ // Owner controller lookups are cached by UID across poll iterations. We don't expect the owners to actually change
643+ // during the lifetime of the wait, so caching the owners list optimises for kube API call volumes.
644+ func (r * Reconfigure ) waitForPodsWithParentNodeSelector (nodeSelectorKey string , timeout time.Duration ) error {
645+ // ownerMatchCache maps owner UID → whether its nodeSelector contains nodeSelectorKey.
646+ // Owner controllers are stable during the wait; only the pods themselves are terminating.
647+ ownerMatchCache := make (map [types.UID ]bool )
648+
649+ return wait .PollUntilContextTimeout (r .ctx , 5 * time .Second , timeout , true , func (ctx context.Context ) (bool , error ) {
650+ podList , err := r .clientset .CoreV1 ().Pods (corev1 .NamespaceAll ).List (ctx , metav1.ListOptions {
651+ FieldSelector : "spec.nodeName=" + r .opts .NodeName ,
652+ })
653+ if err != nil {
654+ return false , fmt .Errorf ("failed to list pods on node %s: %w" , r .opts .NodeName , err )
655+ }
656+
657+ matchCount := 0
658+ for _ , pod := range podList .Items {
659+ for _ , ownerRef := range pod .OwnerReferences {
660+ matches , ok := ownerMatchCache [ownerRef .UID ]
661+ if ! ok {
662+ nodeSelector , err := r .getOwnerNodeSelector (ctx , pod .Namespace , ownerRef )
663+ if err != nil {
664+ log .Warnf ("transient error checking parent nodeSelector for pod %s/%s, will retry: %v" , pod .Namespace , pod .Name , err )
665+ return false , nil
666+ }
667+ _ , matches = nodeSelector [nodeSelectorKey ]
668+ ownerMatchCache [ownerRef .UID ] = matches
669+ }
670+ if matches {
671+ matchCount ++
672+ break
673+ }
674+ }
675+ }
676+
677+ if matchCount > 0 {
678+ log .Infof ("Waiting for %d pod(s) with parent nodeSelector key %s to terminate" , matchCount , nodeSelectorKey )
679+ }
680+ return matchCount == 0 , nil
681+ })
682+ }
683+
684+ // getOwnerNodeSelector returns the pod-template nodeSelector for the given owner reference.
685+ // For ReplicaSets, it traces back to the owning Deployment when present.
686+ func (r * Reconfigure ) getOwnerNodeSelector (ctx context.Context , namespace string , ownerRef metav1.OwnerReference ) (map [string ]string , error ) {
687+ switch ownerRef .Kind {
688+ case "DaemonSet" :
689+ ds , err := r .clientset .AppsV1 ().DaemonSets (namespace ).Get (ctx , ownerRef .Name , metav1.GetOptions {})
690+ if apierrors .IsNotFound (err ) {
691+ return nil , nil
692+ }
693+ if err != nil {
694+ return nil , fmt .Errorf ("failed to get DaemonSet %s/%s: %w" , namespace , ownerRef .Name , err )
695+ }
696+ return ds .Spec .Template .Spec .NodeSelector , nil
697+ default :
698+ return nil , nil
699+ }
700+ }
701+
624702func (r * Reconfigure ) runNvidiaSMI () error {
625703 if r .opts .DriverRootCtrPath == r .opts .DevRootCtrPath {
626704 cmd := exec .Command ("chroot" , r .opts .DriverRootCtrPath , "nvidia-smi" )
0 commit comments