@@ -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+
8799func (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+
8441011func toJSON (obj interface {}) string {
8451012 data , err := json .Marshal (obj )
8461013 if err != nil {
0 commit comments