Skip to content

Commit 71d0434

Browse files
committed
e2e: serial: add serial test for Performance Profile updates and NRT reconciliation
Signed-off-by: Sargun Narula <snarula@redhat.com>
1 parent 792c7fb commit 71d0434

2 files changed

Lines changed: 203 additions & 0 deletions

File tree

test/e2e/serial/tests/configuration.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
k8swait "k8s.io/apimachinery/pkg/util/wait"
3636
"k8s.io/klog/v2"
3737
"sigs.k8s.io/controller-runtime/pkg/client"
38+
"sigs.k8s.io/yaml"
3839

3940
"github.com/google/go-cmp/cmp"
4041
depnodes "github.com/k8stopologyawareschedwg/deployer/pkg/clientutil/nodes"
@@ -56,13 +57,16 @@ import (
5657
"github.com/openshift-kni/numaresources-operator/pkg/validation"
5758
rteconfig "github.com/openshift-kni/numaresources-operator/rte/pkg/config"
5859
"github.com/openshift-kni/numaresources-operator/test/e2e/label"
60+
e2eclient "github.com/openshift-kni/numaresources-operator/test/internal/clients"
5961
"github.com/openshift-kni/numaresources-operator/test/internal/configuration"
6062
e2efixture "github.com/openshift-kni/numaresources-operator/test/internal/fixture"
6163
e2enrt "github.com/openshift-kni/numaresources-operator/test/internal/noderesourcetopologies"
6264
"github.com/openshift-kni/numaresources-operator/test/internal/nrosched"
6365
"github.com/openshift-kni/numaresources-operator/test/internal/objects"
6466

6567
serialconfig "github.com/openshift-kni/numaresources-operator/test/e2e/serial/config"
68+
"github.com/openshift-kni/numaresources-operator/test/internal/hypershift"
69+
"github.com/openshift-kni/numaresources-operator/test/internal/nodepools"
6670
)
6771

6872
/*
@@ -84,6 +88,14 @@ type mcpInfo struct {
8488
sampleNode corev1.Node
8589
}
8690

91+
type Tuning struct {
92+
Profile struct {
93+
CPU struct {
94+
TopologyManagerPolicy string `yaml:"topologyManagerPolicy"`
95+
} `yaml:"cpu"`
96+
} `yaml:"profile"`
97+
}
98+
8799
func (i mcpInfo) ToString() string {
88100
mcpname := ""
89101
if i.mcpObj != nil {
@@ -286,6 +298,153 @@ var _ = Describe("[serial][disruptive] numaresources configuration management",
286298
Expect(schedOK).To(BeTrue(), "pod %s/%s not scheduled with expected scheduler %s", updatedPod.Namespace, updatedPod.Name, serialconfig.SchedulerTestName)
287299
})
288300

301+
It("[test_id:80821] Verify Performance Profile updates on management cluster reflect in NRT resources", Label("reboot_required", label.Slow, label.HyperShift), func() {
302+
fxt.IsRebootTest = true
303+
304+
By("fetching the initial TM policy set in NRT")
305+
initialNrtList := nrtv1alpha2.NodeResourceTopologyList{}
306+
initialNrtList, err := e2enrt.GetUpdated(fxt.Client, initialNrtList, timeout)
307+
Expect(err).ToNot(HaveOccurred(), "cannot get any NodeResourceTopology object from the cluster")
308+
Expect(initialNrtList.Items).ToNot(BeEmpty(), "no NodeResourceTopology items found")
309+
310+
initialNrt := initialNrtList.Items[0]
311+
var initialTMPolicy string
312+
for _, attr := range initialNrt.Attributes {
313+
if attr.Name == "topologyManagerPolicy" {
314+
initialTMPolicy = attr.Value
315+
break
316+
}
317+
}
318+
Expect(initialTMPolicy).ToNot(BeEmpty(), "topologyManagerPolicy not found in initial NRT")
319+
newTMPolicy := getNewTopologyManagerPolicyValue(initialTMPolicy)
320+
Expect(initialTMPolicy).ToNot(Equal(newTMPolicy), "new TM policy should differ from the initial one")
321+
322+
By("retrieving the NodePool from the management cluster")
323+
hostedClusterName, err := hypershift.GetHostedClusterName()
324+
Expect(err).ToNot(HaveOccurred())
325+
nodePool, err := nodepools.GetByClusterName(context.TODO(), e2eclient.MNGClient, hostedClusterName)
326+
Expect(err).ToNot(HaveOccurred())
327+
328+
By("determining whether to update tuningConfig or kubeletConfig")
329+
isTuningConfig := len(nodePool.Spec.TuningConfig) > 0
330+
isKubeletConfig := len(nodePool.Spec.Config) > 0
331+
332+
Expect(isTuningConfig || isKubeletConfig).To(BeTrue(), "Neither tuningConfig nor kubeletConfig is defined in the NodePool")
333+
334+
switch {
335+
case isTuningConfig:
336+
By("updating topologyManagerPolicy in tuningConfig")
337+
tuningConfigName := nodePool.Spec.TuningConfig[0].Name
338+
Expect(tuningConfigName).ToNot(BeEmpty(), "NodePool tuningConfig name is empty")
339+
340+
cmList := &corev1.ConfigMapList{}
341+
err = e2eclient.MNGClient.List(context.TODO(), cmList, &client.ListOptions{
342+
Namespace: "clusters",
343+
})
344+
Expect(err).ToNot(HaveOccurred(), "failed to list ConfigMaps in namespace: clusters")
345+
346+
var targetCM *corev1.ConfigMap
347+
for i := range cmList.Items {
348+
if cmList.Items[i].Name == tuningConfigName {
349+
targetCM = &cmList.Items[i]
350+
break
351+
}
352+
}
353+
Expect(targetCM).ToNot(BeNil(), fmt.Sprintf("ConfigMap %q not found in namespace: clusters", tuningConfigName))
354+
355+
By("updating the ConfigMap with the new TM policy")
356+
yamlData := targetCM.Data["tuning"]
357+
Expect(yamlData).ToNot(BeEmpty(), "tuning section not found in ConfigMap")
358+
359+
var tuning Tuning
360+
err = yaml.Unmarshal([]byte(yamlData), &tuning)
361+
Expect(err).ToNot(HaveOccurred(), "failed to unmarshal ConfigMap tuning YAML")
362+
Expect(tuning.Profile.CPU.TopologyManagerPolicy).To(Equal(initialTMPolicy), "unexpected initial TM policy in parsed YAML")
363+
364+
tuning.Profile.CPU.TopologyManagerPolicy = newTMPolicy
365+
366+
modifiedYamlBytes, err := yaml.Marshal(&tuning)
367+
Expect(err).ToNot(HaveOccurred(), "failed to marshal modified tuning YAML")
368+
targetCM.Data["tuning"] = string(modifiedYamlBytes)
369+
err = e2eclient.MNGClient.Update(context.TODO(), targetCM)
370+
Expect(err).ToNot(HaveOccurred(), "failed to update the ConfigMap with new TM policy")
371+
372+
case isKubeletConfig:
373+
By("updating topologyManagerPolicy in kubeletConfig")
374+
375+
var kubeletConfigCM *corev1.ConfigMap
376+
cmList := &corev1.ConfigMapList{}
377+
err = e2eclient.MNGClient.List(context.TODO(), cmList, &client.ListOptions{
378+
Namespace: "clusters",
379+
})
380+
Expect(err).ToNot(HaveOccurred(), "failed to list ConfigMaps in namespace: clusters")
381+
382+
for _, cfgRef := range nodePool.Spec.Config {
383+
for i := range cmList.Items {
384+
if cmList.Items[i].Name == cfgRef.Name {
385+
kubeletConfigCM = &cmList.Items[i]
386+
break
387+
}
388+
}
389+
if kubeletConfigCM != nil {
390+
break
391+
}
392+
}
393+
Expect(kubeletConfigCM).ToNot(BeNil(), "kubeletConfig ConfigMap referenced by NodePool not found")
394+
395+
kubeletYaml := kubeletConfigCM.Data["config"]
396+
Expect(kubeletYaml).ToNot(BeEmpty(), "kubeletConfig 'config' data is empty in ConfigMap")
397+
398+
var kubeletConfig map[string]interface{}
399+
err = yaml.Unmarshal([]byte(kubeletYaml), &kubeletConfig)
400+
Expect(err).ToNot(HaveOccurred(), "failed to unmarshal kubelet config YAML")
401+
402+
spec, ok := kubeletConfig["spec"].(map[string]interface{})
403+
Expect(ok).To(BeTrue(), "spec section not found in kubelet config")
404+
405+
kubeletSpec, ok := spec["kubeletConfig"].(map[string]interface{})
406+
Expect(ok).To(BeTrue(), "kubeletConfig section not found in spec")
407+
408+
tmPolicy, ok := kubeletSpec["topologyManagerPolicy"].(string)
409+
Expect(ok).To(BeTrue(), "topologyManagerPolicy not found in kubeletConfig")
410+
Expect(tmPolicy).To(Equal(initialTMPolicy), "unexpected initial TM policy in kubeletConfig")
411+
412+
kubeletSpec["topologyManagerPolicy"] = newTMPolicy
413+
414+
modifiedYamlBytes, err := yaml.Marshal(kubeletConfig)
415+
Expect(err).ToNot(HaveOccurred(), "failed to marshal modified kubelet config")
416+
kubeletConfigCM.Data["config"] = string(modifiedYamlBytes)
417+
418+
err = e2eclient.MNGClient.Update(context.TODO(), kubeletConfigCM)
419+
Expect(err).ToNot(HaveOccurred(), "failed to update kubeletConfig ConfigMap with new TM policy")
420+
}
421+
422+
By("Waiting for the node pool configuration to start updating")
423+
err = nodepools.WaitForUpdatingConfig(context.TODO(), e2eclient.MNGClient, nodePool.Name, nodePool.Namespace)
424+
Expect(err).ToNot(HaveOccurred(), "NodePool did not enter UpdatingConfig condition")
425+
426+
By("Waiting for the node pool configuration to be ready")
427+
err = nodepools.WaitForConfigToBeReady(context.TODO(), e2eclient.MNGClient, nodePool.Name, nodePool.Namespace)
428+
Expect(err).ToNot(HaveOccurred(), "NodePool did not exit UpdatingConfig condition")
429+
430+
By("waiting for the NRT to reflect the new TM policy")
431+
Eventually(func(g Gomega) {
432+
updatedNrtList := nrtv1alpha2.NodeResourceTopologyList{}
433+
updatedNrtList, err = e2enrt.GetUpdated(fxt.Client, updatedNrtList, timeout)
434+
g.Expect(err).ToNot(HaveOccurred())
435+
g.Expect(updatedNrtList.Items).ToNot(BeEmpty())
436+
437+
var updatedTM string
438+
for _, attr := range updatedNrtList.Items[0].Attributes {
439+
if attr.Name == "topologyManagerPolicy" {
440+
updatedTM = attr.Value
441+
break
442+
}
443+
}
444+
g.Expect(updatedTM).To(Equal(newTMPolicy), "Expected updated TM policy %q, but got %q", newTMPolicy, updatedTM)
445+
}, 10*time.Minute, 25*time.Second).Should(Succeed(), "NRT did not reflect updated TM policy in time")
446+
})
447+
289448
It("should report the NodeGroupConfig in the status", Label("tier2", "openshift"), func() {
290449
nroKey := objects.NROObjectKey()
291450
nroOperObj := nropv1.NUMAResourcesOperator{}
@@ -841,6 +1000,14 @@ func objRefListToStringList(objRefs []configv1.ObjectReference) []string {
8411000
return ret
8421001
}
8431002

1003+
func getNewTopologyManagerPolicyValue(oldTopologyManagerPolicyValue string) string {
1004+
newTopologyManagerPolicyValue := "best-effort"
1005+
if oldTopologyManagerPolicyValue == newTopologyManagerPolicyValue || oldTopologyManagerPolicyValue == "" {
1006+
newTopologyManagerPolicyValue = "single-numa-node"
1007+
}
1008+
return newTopologyManagerPolicyValue
1009+
}
1010+
8441011
func toJSON(obj interface{}) string {
8451012
data, err := json.Marshal(obj)
8461013
if err != nil {

test/internal/nodepools/nodepools.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package nodepools
33
import (
44
"context"
55
"fmt"
6+
"time"
67

78
corev1 "k8s.io/api/core/v1"
9+
"k8s.io/apimachinery/pkg/util/wait"
810
"sigs.k8s.io/controller-runtime/pkg/client"
911

1012
"github.com/openshift-kni/numaresources-operator/test/internal/hypershift"
@@ -83,3 +85,37 @@ func DeAttachConfigObject(ctx context.Context, cli client.Client, object client.
8385
}
8486
return nil
8587
}
88+
89+
func WaitForUpdatingConfig(ctx context.Context, c client.Client, npName, namespace string) error {
90+
return waitForCondition(ctx, c, npName, namespace, func(conds []hypershiftv1beta1.NodePoolCondition) bool {
91+
for _, cond := range conds {
92+
if cond.Type == hypershiftv1beta1.NodePoolUpdatingConfigConditionType {
93+
return cond.Status == corev1.ConditionTrue
94+
}
95+
}
96+
return false
97+
})
98+
}
99+
100+
func WaitForConfigToBeReady(ctx context.Context, c client.Client, npName, namespace string) error {
101+
return waitForCondition(ctx, c, npName, namespace, func(conds []hypershiftv1beta1.NodePoolCondition) bool {
102+
for _, cond := range conds {
103+
if cond.Type == hypershiftv1beta1.NodePoolUpdatingConfigConditionType {
104+
return cond.Status == corev1.ConditionFalse
105+
}
106+
}
107+
return false
108+
})
109+
}
110+
111+
func waitForCondition(ctx context.Context, c client.Client, npName, namespace string, conditionFunc func([]hypershiftv1beta1.NodePoolCondition) bool) error {
112+
return wait.PollUntilContextTimeout(ctx, 10*time.Second, 60*time.Minute, false, func(ctx context.Context) (done bool, err error) {
113+
np := &hypershiftv1beta1.NodePool{}
114+
key := client.ObjectKey{Name: npName, Namespace: namespace}
115+
err = c.Get(ctx, key, np)
116+
if err != nil {
117+
return false, err
118+
}
119+
return conditionFunc(np.Status.Conditions), nil
120+
})
121+
}

0 commit comments

Comments
 (0)