Skip to content

Commit 5c7c469

Browse files
committed
nrt: cache: test: add common tests for cache impls
Regardless of the cache implementation, GetCachedNRTCopy must behave the same in specific test cases. Add infra to test all the implementations against these cases, while leaving room to add cache-implementation specific changes. Behaves like a mini conformance-suite for caches. Signed-off-by: Francesco Romani <[email protected]>
1 parent aa79c7f commit 5c7c469

File tree

1 file changed

+209
-0
lines changed

1 file changed

+209
-0
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cache
18+
19+
import (
20+
"context"
21+
"encoding/json"
22+
"testing"
23+
24+
topologyv1alpha2 "github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/apis/topology/v1alpha2"
25+
26+
corev1 "k8s.io/api/core/v1"
27+
"k8s.io/apimachinery/pkg/api/resource"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/runtime"
30+
podlisterv1 "k8s.io/client-go/listers/core/v1"
31+
32+
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
33+
tu "sigs.k8s.io/scheduler-plugins/test/util"
34+
)
35+
36+
type testCaseGetCachedNRTCopy struct {
37+
name string
38+
nodeTopologies []*topologyv1alpha2.NodeResourceTopology
39+
nodeName string
40+
hasForeignPods bool
41+
expectedNRT *topologyv1alpha2.NodeResourceTopology
42+
expectedOK bool
43+
}
44+
45+
func checkGetCachedNRTCopy(t *testing.T, makeCache func(client ctrlclient.Client, podLister podlisterv1.PodLister) (Interface, error), extraCases ...testCaseGetCachedNRTCopy) {
46+
t.Helper()
47+
48+
testNodeName := "worker-node-1"
49+
nrt := makeTestNRT(testNodeName)
50+
pod := &corev1.Pod{} // API placeholder
51+
ctx := context.Background()
52+
fakePodLister := &fakePodLister{}
53+
54+
testCases := []testCaseGetCachedNRTCopy{
55+
{
56+
name: "empty",
57+
nodeName: testNodeName,
58+
expectedNRT: nil,
59+
expectedOK: true, // because there's no data, and the information is fresh
60+
},
61+
{
62+
name: "data present",
63+
nodeTopologies: []*topologyv1alpha2.NodeResourceTopology{
64+
nrt,
65+
},
66+
nodeName: testNodeName,
67+
expectedNRT: nrt,
68+
expectedOK: true,
69+
},
70+
{
71+
name: "data missing for node",
72+
nodeTopologies: []*topologyv1alpha2.NodeResourceTopology{
73+
nrt,
74+
},
75+
nodeName: "invalid-node",
76+
expectedNRT: nil,
77+
expectedOK: true, // because there's no data, and the information is fresh
78+
},
79+
}
80+
testCases = append(testCases, extraCases...)
81+
82+
for _, tc := range testCases {
83+
t.Run(tc.name, func(t *testing.T) {
84+
objs := make([]runtime.Object, 0, len(tc.nodeTopologies))
85+
for _, nrt := range tc.nodeTopologies {
86+
objs = append(objs, nrt)
87+
}
88+
89+
fakeClient, err := tu.NewFakeClient(objs...)
90+
if err != nil {
91+
t.Fatal(err)
92+
}
93+
94+
nrtCache, err := makeCache(fakeClient, fakePodLister)
95+
if err != nil {
96+
t.Fatalf("unexpected error creating cache: %v", err)
97+
}
98+
99+
if tc.hasForeignPods {
100+
nrtCache.NodeHasForeignPods(tc.nodeName, pod)
101+
}
102+
103+
gotNRT, gotOK := nrtCache.GetCachedNRTCopy(ctx, tc.nodeName, pod)
104+
105+
if gotOK != tc.expectedOK {
106+
t.Fatalf("unexpected object status from cache: got: %v expected: %v", gotOK, tc.expectedOK)
107+
}
108+
if gotNRT != nil && tc.expectedNRT == nil {
109+
t.Fatalf("object from cache not nil but expected nil")
110+
}
111+
if gotNRT == nil && tc.expectedNRT != nil {
112+
t.Fatalf("object from cache nil but expected not nil")
113+
}
114+
115+
gotJSON := dumpNRT(gotNRT)
116+
expJSON := dumpNRT(tc.expectedNRT)
117+
if gotJSON != expJSON {
118+
t.Fatalf("unexpected object from cache\ngot: %s\nexpected: %s\n", gotJSON, expJSON)
119+
}
120+
})
121+
}
122+
}
123+
124+
func makeTestNRT(nodeName string) *topologyv1alpha2.NodeResourceTopology {
125+
return &topologyv1alpha2.NodeResourceTopology{
126+
TypeMeta: metav1.TypeMeta{
127+
Kind: "NodeResourceTopology",
128+
APIVersion: "topology.node.k8s.io/v1alpha2",
129+
},
130+
ObjectMeta: metav1.ObjectMeta{
131+
Name: nodeName,
132+
},
133+
Attributes: topologyv1alpha2.AttributeList{
134+
{
135+
Name: "topologyManagerPolicy",
136+
Value: "single-numa-node",
137+
},
138+
{
139+
Name: "topologyManagerScope",
140+
Value: "container",
141+
},
142+
},
143+
Zones: topologyv1alpha2.ZoneList{
144+
{
145+
Name: "node-0",
146+
Type: "Node",
147+
Resources: topologyv1alpha2.ResourceInfoList{
148+
MakeTopologyResInfo(cpu, "32", "30"),
149+
MakeTopologyResInfo(memory, "32Gi", "32Gi"),
150+
MakeTopologyResInfo(nicResourceName, "8", "8"),
151+
},
152+
},
153+
{
154+
Name: "node-1",
155+
Type: "Node",
156+
Resources: topologyv1alpha2.ResourceInfoList{
157+
MakeTopologyResInfo(cpu, "32", "30"),
158+
MakeTopologyResInfo(memory, "32Gi", "32Gi"),
159+
MakeTopologyResInfo(nicResourceName, "8", "8"),
160+
},
161+
},
162+
},
163+
}
164+
}
165+
166+
func dumpNRT(nrtObj *topologyv1alpha2.NodeResourceTopology) string {
167+
nrtJson, err := json.MarshalIndent(nrtObj, "", " ")
168+
if err != nil {
169+
return "marshallingError"
170+
}
171+
return string(nrtJson)
172+
}
173+
174+
func MakeTopologyResInfo(name, capacity, available string) topologyv1alpha2.ResourceInfo {
175+
return topologyv1alpha2.ResourceInfo{
176+
Name: name,
177+
Capacity: resource.MustParse(capacity),
178+
Available: resource.MustParse(available),
179+
}
180+
}
181+
182+
func makeDefaultTestTopology() []*topologyv1alpha2.NodeResourceTopology {
183+
return []*topologyv1alpha2.NodeResourceTopology{
184+
{
185+
ObjectMeta: metav1.ObjectMeta{Name: "node1"},
186+
TopologyPolicies: []string{string(topologyv1alpha2.SingleNUMANodeContainerLevel)},
187+
Zones: topologyv1alpha2.ZoneList{
188+
{
189+
Name: "node-0",
190+
Type: "Node",
191+
Resources: topologyv1alpha2.ResourceInfoList{
192+
MakeTopologyResInfo(cpu, "32", "30"),
193+
MakeTopologyResInfo(memory, "64Gi", "60Gi"),
194+
MakeTopologyResInfo(nicResourceName, "16", "16"),
195+
},
196+
},
197+
{
198+
Name: "node-1",
199+
Type: "Node",
200+
Resources: topologyv1alpha2.ResourceInfoList{
201+
MakeTopologyResInfo(cpu, "32", "30"),
202+
MakeTopologyResInfo(memory, "64Gi", "60Gi"),
203+
MakeTopologyResInfo(nicResourceName, "16", "16"),
204+
},
205+
},
206+
},
207+
},
208+
}
209+
}

0 commit comments

Comments
 (0)