Skip to content

Commit 2d8ab12

Browse files
committed
add support for bouncing third-party gpu client pods during mig config apply
Signed-off-by: Tariq Ibrahim <tibrahim@nvidia.com>
1 parent b52cf9c commit 2d8ab12

1 file changed

Lines changed: 78 additions & 0 deletions

File tree

pkg/mig/reconfigure/reconfigure.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
624702
func (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

Comments
 (0)