Skip to content

Commit 23d6f73

Browse files
committed
extended resource backed by DRA: test
1 parent 34a64db commit 23d6f73

File tree

13 files changed

+590
-33
lines changed

13 files changed

+590
-33
lines changed

test/compatibility_lifecycle/reference/versioned_feature_list.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,12 @@
479479
lockToDefault: false
480480
preRelease: Alpha
481481
version: "1.33"
482+
- name: DRAExtendedResource
483+
versionedSpecs:
484+
- default: false
485+
lockToDefault: false
486+
preRelease: Alpha
487+
version: "1.34"
482488
- name: DRAPartitionableDevices
483489
versionedSpecs:
484490
- default: false

test/e2e/dra/dra.go

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1806,7 +1806,6 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), func() {
18061806
createdTaint := b.Create(ctx, taint)
18071807
taint = createdTaint[0].(*resourcealphaapi.DeviceTaintRule)
18081808
gomega.Expect(*taint).Should(gomega.HaveField("Spec.Taint.TimeAdded.Time", gomega.BeTemporally("~", time.Now(), time.Minute /* allow for some clock drift and delays */)))
1809-
18101809
framework.ExpectNoError(e2epod.WaitForPodTerminatingInNamespaceTimeout(ctx, f.ClientSet, pod.Name, f.Namespace.Name, f.Timeouts.PodStart))
18111810
pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(ctx, pod.Name, metav1.GetOptions{})
18121811
framework.ExpectNoError(err, "get pod")
@@ -1820,6 +1819,177 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), func() {
18201819
})
18211820
})
18221821

1822+
framework.Context(f.WithFeatureGate(features.DRAExtendedResource), func() {
1823+
nodes := drautils.NewNodes(f, 1, 1)
1824+
driver := drautils.NewDriver(f, nodes, drautils.NetworkResources(10, false))
1825+
b := drautils.NewBuilder(f, driver)
1826+
b.UseExtendedResourceName = true
1827+
1828+
ginkgo.It("must run a pod with extended resource with one container one resource", func(ctx context.Context) {
1829+
pod := b.Pod()
1830+
res := v1.ResourceList{}
1831+
res[v1.ResourceName(drautils.ExtendedResourceName(0))] = resource.MustParse("1")
1832+
pod.Spec.Containers[0].Resources.Requests = res
1833+
pod.Spec.Containers[0].Resources.Limits = res
1834+
1835+
b.Create(ctx, pod)
1836+
err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod)
1837+
framework.ExpectNoError(err, "start pod")
1838+
containerEnv := []string{
1839+
"container_0_request_0", "true",
1840+
}
1841+
drautils.TestContainerEnv(ctx, f, pod, pod.Spec.Containers[0].Name, false, containerEnv...)
1842+
})
1843+
1844+
ginkgo.It("must run a pod with extended resource with one container three resources", func(ctx context.Context) {
1845+
pod := b.Pod()
1846+
res := v1.ResourceList{}
1847+
for i := range 3 {
1848+
res[v1.ResourceName(drautils.ExtendedResourceName(i))] = resource.MustParse("1")
1849+
}
1850+
pod.Spec.Containers[0].Resources.Requests = res
1851+
pod.Spec.Containers[0].Resources.Limits = res
1852+
1853+
b.Create(ctx, pod)
1854+
err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod)
1855+
framework.ExpectNoError(err, "start pod")
1856+
containerEnv := []string{
1857+
"container_0_request_0", "true",
1858+
"container_0_request_1", "true",
1859+
"container_0_request_2", "true",
1860+
}
1861+
drautils.TestContainerEnv(ctx, f, pod, pod.Spec.Containers[0].Name, false, containerEnv...)
1862+
})
1863+
ginkgo.It("must run a pod with extended resource with three containers one resource each", func(ctx context.Context) {
1864+
pod := b.Pod()
1865+
pod.Spec.Containers = append(pod.Spec.Containers, *pod.Spec.Containers[0].DeepCopy())
1866+
pod.Spec.Containers = append(pod.Spec.Containers, *pod.Spec.Containers[0].DeepCopy())
1867+
pod.Spec.Containers[0].Name = "container0"
1868+
pod.Spec.Containers[1].Name = "container1"
1869+
pod.Spec.Containers[2].Name = "container2"
1870+
1871+
for i := range 3 {
1872+
res := v1.ResourceList{}
1873+
res[v1.ResourceName(drautils.ExtendedResourceName(i))] = resource.MustParse("1")
1874+
pod.Spec.Containers[i].Resources.Requests = res
1875+
pod.Spec.Containers[i].Resources.Limits = res
1876+
}
1877+
1878+
b.Create(ctx, pod)
1879+
err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod)
1880+
framework.ExpectNoError(err, "start pod")
1881+
for i := range 3 {
1882+
containerEnv := []string{
1883+
fmt.Sprintf("container_%d_request_0", i), "true",
1884+
}
1885+
drautils.TestContainerEnv(ctx, f, pod, pod.Spec.Containers[i].Name, false, containerEnv...)
1886+
}
1887+
})
1888+
ginkgo.It("must run a pod with extended resource with three containers multiple resources each", func(ctx context.Context) {
1889+
pod := b.Pod()
1890+
pod.Spec.Containers = append(pod.Spec.Containers, *pod.Spec.Containers[0].DeepCopy())
1891+
pod.Spec.Containers = append(pod.Spec.Containers, *pod.Spec.Containers[0].DeepCopy())
1892+
pod.Spec.Containers[0].Name = "container0"
1893+
pod.Spec.Containers[1].Name = "container1"
1894+
pod.Spec.Containers[2].Name = "container2"
1895+
1896+
res := v1.ResourceList{}
1897+
res[v1.ResourceName(drautils.ExtendedResourceName(0))] = resource.MustParse("1")
1898+
pod.Spec.Containers[0].Resources.Requests = res
1899+
pod.Spec.Containers[0].Resources.Limits = res
1900+
res = v1.ResourceList{}
1901+
res[v1.ResourceName(drautils.ExtendedResourceName(1))] = resource.MustParse("1")
1902+
res[v1.ResourceName(drautils.ExtendedResourceName(2))] = resource.MustParse("1")
1903+
pod.Spec.Containers[1].Resources.Requests = res
1904+
pod.Spec.Containers[1].Resources.Limits = res
1905+
res = v1.ResourceList{}
1906+
res[v1.ResourceName(drautils.ExtendedResourceName(3))] = resource.MustParse("1")
1907+
res[v1.ResourceName(drautils.ExtendedResourceName(4))] = resource.MustParse("1")
1908+
res[v1.ResourceName(drautils.ExtendedResourceName(5))] = resource.MustParse("1")
1909+
pod.Spec.Containers[2].Resources.Requests = res
1910+
pod.Spec.Containers[2].Resources.Limits = res
1911+
1912+
b.Create(ctx, pod)
1913+
err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod)
1914+
framework.ExpectNoError(err, "start pod")
1915+
containerEnv := []string{
1916+
"container_0_request_0", "true",
1917+
}
1918+
drautils.TestContainerEnv(ctx, f, pod, pod.Spec.Containers[0].Name, false, containerEnv...)
1919+
containerEnv = []string{
1920+
"container_1_request_0", "true",
1921+
"container_1_request_1", "true",
1922+
}
1923+
drautils.TestContainerEnv(ctx, f, pod, pod.Spec.Containers[1].Name, false, containerEnv...)
1924+
containerEnv = []string{
1925+
"container_2_request_0", "true",
1926+
"container_2_request_1", "true",
1927+
"container_2_request_2", "true",
1928+
}
1929+
drautils.TestContainerEnv(ctx, f, pod, pod.Spec.Containers[2].Name, false, containerEnv...)
1930+
})
1931+
})
1932+
1933+
framework.Context(f.WithFeatureGate(features.DRAExtendedResource), func() {
1934+
nodes := drautils.NewNodes(f, 2, 2)
1935+
nodes.NumReservedNodes = 1
1936+
driver := drautils.NewDriver(f, nodes, drautils.NetworkResources(2, false))
1937+
b := drautils.NewBuilder(f, driver)
1938+
b.UseExtendedResourceName = true
1939+
1940+
// This test needs the entire test cluster for itself, one node in the cluster
1941+
// is deployed device plugin for the test, therefore it is marked as serial.
1942+
// The test runs two pods, one pod request extended resource backed by DRA,
1943+
// the other pod requests extended resource by device plugin.
1944+
f.It("must run pods with extended resource on dra nodes and device plugin nodes", f.WithSerial(), func(ctx context.Context) {
1945+
extendedResourceName := deployDevicePlugin(ctx, f, nodes.ExtraNodeNames)
1946+
// drautils.ExtendedResourceName(-1) must be the same as the returned extendedResourceName
1947+
// drautils.ExtendedResourceName(-1) is used for DRA drivers
1948+
// extendedResourceName is used for device plugin.
1949+
gomega.Expect(string(extendedResourceName)).To(gomega.Equal(drautils.ExtendedResourceName(-1)))
1950+
1951+
pod1 := b.Pod()
1952+
res := v1.ResourceList{}
1953+
res[v1.ResourceName(drautils.ExtendedResourceName(-1))] = resource.MustParse("2")
1954+
pod1.Spec.Containers[0].Resources.Requests = res
1955+
pod1.Spec.Containers[0].Resources.Limits = res
1956+
b.Create(ctx, pod1)
1957+
1958+
pod2 := b.Pod()
1959+
pod2.Spec.Containers[0].Resources.Requests = res
1960+
pod2.Spec.Containers[0].Resources.Limits = res
1961+
b.Create(ctx, pod2)
1962+
1963+
err := e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod1)
1964+
framework.ExpectNoError(err, "start pod1")
1965+
err = e2epod.WaitForPodRunningInNamespace(ctx, f.ClientSet, pod2)
1966+
framework.ExpectNoError(err, "start pod2")
1967+
1968+
scheduledPod1, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(ctx, pod1.Name, metav1.GetOptions{})
1969+
gomega.Expect(scheduledPod1).ToNot(gomega.BeNil())
1970+
framework.ExpectNoError(err)
1971+
1972+
scheduledPod2, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(ctx, pod2.Name, metav1.GetOptions{})
1973+
gomega.Expect(scheduledPod2).ToNot(gomega.BeNil())
1974+
framework.ExpectNoError(err)
1975+
1976+
draPod := scheduledPod1
1977+
devicePluginPod := scheduledPod2
1978+
if scheduledPod1.Spec.NodeName == nodes.ExtraNodeNames[0] {
1979+
draPod = scheduledPod2
1980+
devicePluginPod = scheduledPod1
1981+
}
1982+
1983+
gomega.Expect(devicePluginPod.Spec.NodeName).To(gomega.Equal(nodes.ExtraNodeNames[0]))
1984+
gomega.Expect(devicePluginPod.Status.ExtendedResourceClaimStatus).To(gomega.BeNil())
1985+
gomega.Expect(draPod.Spec.NodeName).To(gomega.Equal(nodes.NodeNames[0]))
1986+
containerEnv := []string{
1987+
"container_0_request_0", "true",
1988+
}
1989+
drautils.TestContainerEnv(ctx, f, draPod, draPod.Spec.Containers[0].Name, false, containerEnv...)
1990+
})
1991+
})
1992+
18231993
ginkgo.Context("ResourceSlice Controller", func() {
18241994
// This is a stress test for creating many large slices.
18251995
// Each slice is as large as API limits allow.

test/e2e/dra/utils/builder.go

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"regexp"
2424
"sort"
25+
"strconv"
2526
"strings"
2627
"time"
2728

@@ -47,11 +48,27 @@ import (
4748
"k8s.io/utils/ptr"
4849
)
4950

51+
// ExtendedResourceName returns hard coded extended resource name with a variable
52+
// suffix from the input integer when it's greater than or equal to 0.
53+
// "example.com/resource" is not special, any valid extended resource name can be used
54+
// instead, except when using example device plugin in the test, which hard coded it,
55+
// see test/e2e/dra/deploy_device_plugin.go.
56+
// i == -1 is special, the extended resource name has no extra suffix, it is
57+
// used in the test where a cluster has both DRA driver and device plugin.
58+
func ExtendedResourceName(i int) string {
59+
suffix := ""
60+
if i >= 0 {
61+
suffix = strconv.Itoa(i)
62+
}
63+
return "example.com/resource" + suffix
64+
}
65+
5066
// Builder contains a running counter to make objects unique within thir
5167
// namespace.
5268
type Builder struct {
53-
f *framework.Framework
54-
driver *Driver
69+
f *framework.Framework
70+
driver *Driver
71+
UseExtendedResourceName bool
5572

5673
podCounter int
5774
claimCounter int
@@ -65,12 +82,26 @@ func (b *Builder) ClassName() string {
6582

6683
// Class returns the device Class that the builder's other objects
6784
// reference.
68-
func (b *Builder) Class() *resourceapi.DeviceClass {
85+
// The input i is used to pick the extended resource name whose suffix has the
86+
// same i for the device class.
87+
// i == -1 is special, the extended resource name has no extra suffix, it is
88+
// used in the test where a cluster has both DRA driver and device plugin.
89+
func (b *Builder) Class(i int) *resourceapi.DeviceClass {
90+
ern := ExtendedResourceName(i)
91+
name := b.ClassName()
92+
if i >= 0 {
93+
name = b.ClassName() + strconv.Itoa(i)
94+
}
6995
class := &resourceapi.DeviceClass{
7096
ObjectMeta: metav1.ObjectMeta{
71-
Name: b.ClassName(),
97+
Name: name,
7298
},
7399
}
100+
if b.UseExtendedResourceName {
101+
class.Spec = resourceapi.DeviceClassSpec{
102+
ExtendedResourceName: &ern,
103+
}
104+
}
74105
class.Spec.Selectors = []resourceapi.DeviceSelector{{
75106
CEL: &resourceapi.CELDeviceSelector{
76107
Expression: fmt.Sprintf(`device.driver == "%s"`, b.driver.Name),
@@ -415,7 +446,7 @@ func TestContainerEnv(ctx context.Context, f *framework.Framework, pod *v1.Pod,
415446
gomega.Expect(actualEnv).To(gomega.Equal(expectEnv), fmt.Sprintf("container %s env output:\n%s", containerName, stdout))
416447
} else {
417448
for i := 0; i < len(env); i += 2 {
418-
envStr := fmt.Sprintf("\n%s=%s\n", env[i], env[i+1])
449+
envStr := fmt.Sprintf("%s=%s\n", env[i], env[i+1])
419450
gomega.Expect(stdout).To(gomega.ContainSubstring(envStr), fmt.Sprintf("container %s env variables", containerName))
420451
}
421452
}
@@ -436,7 +467,9 @@ func NewBuilderNow(ctx context.Context, f *framework.Framework, driver *Driver)
436467
func (b *Builder) setUp(ctx context.Context) {
437468
b.podCounter = 0
438469
b.claimCounter = 0
439-
b.Create(ctx, b.Class())
470+
for i := -1; i < 6; i++ {
471+
b.Create(ctx, b.Class(i))
472+
}
440473
ginkgo.DeferCleanup(b.tearDown)
441474
}
442475

test/e2e/dra/utils/deploy.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,19 @@ import (
8181
)
8282

8383
type Nodes struct {
84+
// NodeNames has the main set of node names.
8485
NodeNames []string
8586
tempDir string
87+
// NumReservedNodes specifies the desired number of
88+
// extra nodes that get set aside. That many node names
89+
// will be stored in ExtraNodeNames.
90+
//
91+
// Must be <= the minimum number of requested nodes.
92+
NumReservedNodes int
93+
// ExtraNodeNames has exactly as many node names as
94+
// requested via NumReservedNodes. Those nodes are
95+
// different than the nodes listed in NodeNames.
96+
ExtraNodeNames []string
8697
}
8798

8899
// NewNodes selects nodes to run the test on.
@@ -117,10 +128,14 @@ func (nodes *Nodes) init(ctx context.Context, f *framework.Framework, minNodes,
117128
framework.ExpectNoError(err, "get nodes")
118129
numNodes := int32(len(nodeList.Items))
119130
if int(numNodes) < minNodes {
120-
e2eskipper.Skipf("%d ready nodes required, only have %d", minNodes, numNodes)
131+
e2eskipper.Skipf("%d ready nodes required, only have %d", minNodes+nodes.NumReservedNodes, numNodes)
121132
}
122133
nodes.NodeNames = nil
123-
for _, node := range nodeList.Items {
134+
for i, node := range nodeList.Items {
135+
if i < nodes.NumReservedNodes {
136+
nodes.ExtraNodeNames = append(nodes.ExtraNodeNames, node.Name)
137+
continue
138+
}
124139
nodes.NodeNames = append(nodes.NodeNames, node.Name)
125140
}
126141
sort.Strings(nodes.NodeNames)

test/e2e/node/pod_admission.go

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -99,23 +99,24 @@ var _ = SIGDescribe("PodRejectionStatus", func() {
9999
// This detects if there are any new fields in Status that were dropped by the pod rejection.
100100
// These new fields either should be kept by kubelet's admission or added explicitly in the list of fields that are having a different value or must be cleared.
101101
gomega.Expect(gotPod.Status).To(gstruct.MatchAllFields(gstruct.Fields{
102-
"ObservedGeneration": gstruct.Ignore(),
103-
"Phase": gstruct.Ignore(),
104-
"Conditions": gstruct.Ignore(),
105-
"Message": gstruct.Ignore(),
106-
"Reason": gstruct.Ignore(),
107-
"NominatedNodeName": gstruct.Ignore(),
108-
"HostIP": gstruct.Ignore(),
109-
"HostIPs": gstruct.Ignore(),
110-
"PodIP": gstruct.Ignore(),
111-
"PodIPs": gstruct.Ignore(),
112-
"StartTime": gstruct.Ignore(),
113-
"InitContainerStatuses": gstruct.Ignore(),
114-
"ContainerStatuses": gstruct.Ignore(),
115-
"QOSClass": gomega.Equal(pod.Status.QOSClass), // QOSClass should be kept
116-
"EphemeralContainerStatuses": gstruct.Ignore(),
117-
"Resize": gstruct.Ignore(),
118-
"ResourceClaimStatuses": gstruct.Ignore(),
102+
"ObservedGeneration": gstruct.Ignore(),
103+
"Phase": gstruct.Ignore(),
104+
"Conditions": gstruct.Ignore(),
105+
"Message": gstruct.Ignore(),
106+
"Reason": gstruct.Ignore(),
107+
"NominatedNodeName": gstruct.Ignore(),
108+
"HostIP": gstruct.Ignore(),
109+
"HostIPs": gstruct.Ignore(),
110+
"PodIP": gstruct.Ignore(),
111+
"PodIPs": gstruct.Ignore(),
112+
"StartTime": gstruct.Ignore(),
113+
"InitContainerStatuses": gstruct.Ignore(),
114+
"ContainerStatuses": gstruct.Ignore(),
115+
"QOSClass": gomega.Equal(pod.Status.QOSClass), // QOSClass should be kept
116+
"EphemeralContainerStatuses": gstruct.Ignore(),
117+
"Resize": gstruct.Ignore(),
118+
"ResourceClaimStatuses": gstruct.Ignore(),
119+
"ExtendedResourceClaimStatus": gstruct.Ignore(),
119120
}))
120121
})
121122
})

0 commit comments

Comments
 (0)