Skip to content

Commit bda5598

Browse files
committed
e2e: support GPU models on separate nodes
1 parent 8c22dbd commit bda5598

5 files changed

Lines changed: 92 additions & 4 deletions

File tree

e2e/gpu/gpu_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,12 @@ func TestGPU(t *testing.T) {
7474
require.NoError(t, err)
7575

7676
var resources []any
77-
for _, config := range gpuConfigs {
77+
for i, config := range gpuConfigs {
7878
t.Logf("Using GPU resource %s (%s) with quantity %d", config.resource, config.model, config.quantity)
79-
resources = append(resources, kuberesource.GPU(config.deploymentName(), string(config.resource), config.quantity)...)
79+
// The hostpath CSI volume is node-local. Exercise the block-device
80+
// regression once without preventing other GPU models from running on
81+
// their respective nodes.
82+
resources = append(resources, kuberesource.GPU(config.deploymentName(), string(config.resource), config.quantity, i == 0)...)
8083
}
8184

8285
coordinator := kuberesource.CoordinatorBundle()

internal/kuberesource/parts.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ func NodeInstallers(namespace string, platforms []platforms.Platform) ([]*applya
8787
return out, nil
8888
}
8989

90+
func nvidiaGPUToleration() *applycorev1.TolerationApplyConfiguration {
91+
return applycorev1.Toleration().
92+
WithKey("nvidia.com/gpu").
93+
WithOperator(corev1.TolerationOpExists).
94+
WithEffect(corev1.TaintEffectNoSchedule)
95+
}
96+
97+
func nodeInstallerTolerations(platform platforms.Platform) []*applycorev1.TolerationApplyConfiguration {
98+
if !platforms.IsGPU(platform) {
99+
return nil
100+
}
101+
return []*applycorev1.TolerationApplyConfiguration{nvidiaGPUToleration()}
102+
}
103+
90104
// NodeInstaller constructs a node installer daemon set.
91105
func NodeInstaller(namespace string, platform platforms.Platform) (*applyappsv1.DaemonSetApplyConfiguration, error) {
92106
runtimeHandler, err := manifest.RuntimeHandler(platform)
@@ -124,6 +138,7 @@ func NodeInstaller(namespace string, platform platforms.Platform) (*applyappsv1.
124138
WithSpec(
125139
PodSpec().
126140
WithHostPID(true).
141+
WithTolerations(nodeInstallerTolerations(platform)...).
127142
WithInitContainers(
128143
Container().
129144
WithName("installer").

internal/kuberesource/parts_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,69 @@
44
package kuberesource
55

66
import (
7+
"fmt"
78
"testing"
89

10+
"github.com/edgelesssys/contrast/internal/platforms"
911
"github.com/stretchr/testify/require"
12+
corev1 "k8s.io/api/core/v1"
13+
applyappsv1 "k8s.io/client-go/applyconfigurations/apps/v1"
1014
)
1115

16+
func TestNodeInstallerGPUToleration(t *testing.T) {
17+
for _, tc := range []struct {
18+
name string
19+
platform platforms.Platform
20+
wantTolerations int
21+
}{
22+
{
23+
name: "GPU",
24+
platform: platforms.MetalQEMUTDXGPU,
25+
wantTolerations: 1,
26+
},
27+
{
28+
name: "non-GPU",
29+
platform: platforms.MetalQEMUTDX,
30+
},
31+
} {
32+
t.Run(tc.name, func(t *testing.T) {
33+
require := require.New(t)
34+
tolerations := nodeInstallerTolerations(tc.platform)
35+
require.Len(tolerations, tc.wantTolerations)
36+
if tc.wantTolerations == 0 {
37+
return
38+
}
39+
require.Equal("nvidia.com/gpu", *tolerations[0].Key)
40+
require.Equal(corev1.TolerationOpExists, *tolerations[0].Operator)
41+
require.Equal(corev1.TaintEffectNoSchedule, *tolerations[0].Effect)
42+
})
43+
}
44+
}
45+
46+
func TestGPUBlockDevice(t *testing.T) {
47+
for _, withBlockDevice := range []bool{false, true} {
48+
t.Run(fmt.Sprintf("withBlockDevice=%t", withBlockDevice), func(t *testing.T) {
49+
require := require.New(t)
50+
resources := GPU("gpu-tester", "nvidia.com/test", 1, withBlockDevice)
51+
require.Len(resources, 1)
52+
53+
deployment, ok := resources[0].(*applyappsv1.DeploymentApplyConfiguration)
54+
require.True(ok)
55+
spec := deployment.Spec.Template.Spec
56+
require.Len(spec.Containers, 3)
57+
noGPUContainer := spec.Containers[2]
58+
require.Equal("no-gpu", *noGPUContainer.Name)
59+
60+
wantBlockDevices := 0
61+
if withBlockDevice {
62+
wantBlockDevices = 1
63+
}
64+
require.Len(spec.Volumes, wantBlockDevices)
65+
require.Len(noGPUContainer.VolumeDevices, wantBlockDevices)
66+
})
67+
}
68+
}
69+
1270
func TestNewPortForwarder(t *testing.T) {
1371
require := require.New(t)
1472

internal/kuberesource/resourcegen/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func main() {
8787
case "vault":
8888
subResources = kuberesource.PatchRuntimeHandlers(kuberesource.Vault(*namespace), "contrast-cc")
8989
case "gpu":
90-
subResources = kuberesource.PatchRuntimeHandlers(kuberesource.GPU("gpu-tester", *gpuClass, int64(*gpuCount)), "contrast-cc")
90+
subResources = kuberesource.PatchRuntimeHandlers(kuberesource.GPU("gpu-tester", *gpuClass, int64(*gpuCount), true), "contrast-cc")
9191
default:
9292
log.Fatalf("Error: unknown set: %s\n", set)
9393
}

internal/kuberesource/sets.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,8 @@ done
804804
//
805805
// gpuClass must be a vendor/class pair, like nvidia.com/GB100_B200.
806806
// gpuQuantity is the number of GPUs.
807-
func GPU(name string, gpuClass string, gpuQuantity int64) []any {
807+
// withBlockDevice adds a block device to the non-GPU container as a regression test.
808+
func GPU(name string, gpuClass string, gpuQuantity int64, withBlockDevice bool) []any {
808809
component := "gpu-test"
809810
tester := Deployment(name, "").
810811
WithSpec(
@@ -816,6 +817,7 @@ func GPU(name string, gpuClass string, gpuQuantity int64) []any {
816817
WithLabels(SelectorLabels(name, component)).
817818
WithSpec(
818819
PodSpec().
820+
WithTolerations(nvidiaGPUToleration()).
819821
WithContainers(
820822
Container().
821823
WithName("gpu-tester-direct"). // This container directly requests a GPU.
@@ -876,6 +878,16 @@ func GPU(name string, gpuClass string, gpuQuantity int64) []any {
876878
),
877879
),
878880
)
881+
if !withBlockDevice {
882+
tester.Spec.Template.Spec.Volumes = nil
883+
for i := range tester.Spec.Template.Spec.Containers {
884+
container := &tester.Spec.Template.Spec.Containers[i]
885+
if container.Name != nil && *container.Name == "no-gpu" {
886+
container.VolumeDevices = nil
887+
break
888+
}
889+
}
890+
}
879891

880892
return []any{tester}
881893
}

0 commit comments

Comments
 (0)