1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2024-12-15 17:50:49 +00:00

Merge pull request #1049 from jlojosnegros/node-signature

topology-updater:compute pod set fingerprint
This commit is contained in:
Kubernetes Prow Robot 2023-02-22 02:05:58 -08:00 committed by GitHub
commit 163a6dc502
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 374 additions and 43 deletions

View file

@ -139,6 +139,7 @@ func initFlags(flagset *flag.FlagSet) (*topology.Args, *resourcemonitor.Args) {
"Pod Resource Socket path to use.")
flagset.StringVar(&args.ConfigFile, "config", "/etc/kubernetes/node-feature-discovery/nfd-topology-updater.conf",
"Config file to use.")
flagset.BoolVar(&resourcemonitorArgs.PodSetFingerprint, "pods-fingerprint", false, "Compute and report the pod set fingerprint")
klog.InitFlags(flagset)

View file

@ -55,6 +55,9 @@ spec:
- "-key-file=/etc/kubernetes/node-feature-discovery/certs/tls.key"
- "-cert-file=/etc/kubernetes/node-feature-discovery/certs/tls.crt"
{{- end }}
{{- if .Values.topologyUpdater.podSetFingerprint }}
- "-pods-fingerprint"
{{- end }}
volumeMounts:
- name: kubelet-config
mountPath: /host-var/lib/kubelet/config.yaml

View file

@ -424,6 +424,7 @@ topologyUpdater:
tolerations: []
annotations: {}
affinity: {}
podSetFingerprint: true
topologyGC:
enable: true

View file

@ -173,6 +173,7 @@ We have introduced the following Chart parameters.
| `topologyUpdater.annotations` | dict | {} | Topology updater pod [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) |
| `topologyUpdater.affinity` | dict | {} | Topology updater pod [affinity](https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) |
| `topologyUpdater.config` | dict | | [configuration](../reference/topology-updater-configuration-reference) |
| `topologyUpdater.podSetFingerprint` | bool | false | Enables compute and report of pod fingerprint in NRT objects. |
### Topology garbage collector parameters

View file

@ -147,3 +147,16 @@ Example:
```bash
nfd-topology-updater -podresources-socket=/var/lib/kubelet/pod-resources/kubelet.sock
```
### -pods-fingerprint
Enbles the compute and report the pod set fingerprint in the NRT.
A pod fingerprint is a compact representation of the "node state" regarding resources.
Default: `false`
Example:
```bash
nfd-topology-updater -pods-fingerprint
```

2
go.mod
View file

@ -9,6 +9,7 @@ require (
github.com/google/go-cmp v0.5.9
github.com/jaypipes/ghw v0.8.1-0.20210827132705-c7224150a17e
github.com/k8stopologyawareschedwg/noderesourcetopology-api v0.1.0
github.com/k8stopologyawareschedwg/podfingerprint v0.1.2
github.com/klauspost/cpuid/v2 v2.2.4
github.com/onsi/ginkgo/v2 v2.4.0
github.com/onsi/gomega v1.23.0
@ -49,6 +50,7 @@ require (
github.com/Microsoft/go-winio v0.4.17 // indirect
github.com/Microsoft/hcsshim v0.8.22 // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e // indirect

4
go.sum
View file

@ -86,6 +86,8 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
@ -422,6 +424,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/k8stopologyawareschedwg/noderesourcetopology-api v0.1.0 h1:2uCRJbv+A+fmaUaO0wLZ8oYd6cLE1dRzBQcFNxggH3s=
github.com/k8stopologyawareschedwg/noderesourcetopology-api v0.1.0/go.mod h1:AkACMQGiTgCt0lQw3m7TTU8PLH9lYKNK5e9DqFf5VuM=
github.com/k8stopologyawareschedwg/podfingerprint v0.1.2 h1:Db5KLJjPg2mKaCoeEliMlea+JMyDMWdbNPXnWbPNDyM=
github.com/k8stopologyawareschedwg/podfingerprint v0.1.2/go.mod h1:C23pM15t06dXg/OihGlqBvnYzLr+MXDXJ7zMfbNAyXI=
github.com/karrick/godirwalk v1.17.0 h1:b4kY7nqDdioR/6qnbHQyDvmA17u5G1cZ6J+CZXwSWoI=
github.com/karrick/godirwalk v1.17.0/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=

View file

@ -130,7 +130,7 @@ func (w *nfdTopologyUpdater) Run() error {
var resScan resourcemonitor.ResourcesScanner
resScan, err = resourcemonitor.NewPodResourcesScanner(w.resourcemonitorArgs.Namespace, podResClient, w.apihelper)
resScan, err = resourcemonitor.NewPodResourcesScanner(w.resourcemonitorArgs.Namespace, podResClient, w.apihelper, w.resourcemonitorArgs.PodSetFingerprint)
if err != nil {
return fmt.Errorf("failed to initialize ResourceMonitor instance: %w", err)
}
@ -154,16 +154,16 @@ func (w *nfdTopologyUpdater) Run() error {
select {
case <-crTrigger.C:
klog.Infof("Scanning")
podResources, err := resScan.Scan()
utils.KlogDump(1, "podResources are", " ", podResources)
scanResponse, err := resScan.Scan()
utils.KlogDump(1, "podResources are", " ", scanResponse.PodResources)
if err != nil {
klog.Warningf("Scan failed: %v", err)
continue
}
zones = resAggr.Aggregate(podResources)
zones = resAggr.Aggregate(scanResponse.PodResources)
utils.KlogDump(1, "After aggregating resources identified zones are", " ", zones)
if !w.args.NoPublish {
if err = w.updateNodeResourceTopology(zones); err != nil {
if err = w.updateNodeResourceTopology(zones, scanResponse); err != nil {
return err
}
}
@ -188,7 +188,7 @@ func (w *nfdTopologyUpdater) Stop() {
}
}
func (w *nfdTopologyUpdater) updateNodeResourceTopology(zoneInfo v1alpha2.ZoneList) error {
func (w *nfdTopologyUpdater) updateNodeResourceTopology(zoneInfo v1alpha2.ZoneList, scanResponse resourcemonitor.ScanResponse) error {
cli, err := w.apihelper.GetTopologyClient()
if err != nil {
return err
@ -205,6 +205,8 @@ func (w *nfdTopologyUpdater) updateNodeResourceTopology(zoneInfo v1alpha2.ZoneLi
Attributes: createTopologyAttributes(w.nodeInfo.tmPolicy, w.nodeInfo.tmScope),
}
updateAttributes(&nrtNew.Attributes, scanResponse.Attributes)
_, err := cli.TopologyV1alpha2().NodeResourceTopologies().Create(context.TODO(), &nrtNew, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("failed to create NodeResourceTopology: %w", err)
@ -216,6 +218,7 @@ func (w *nfdTopologyUpdater) updateNodeResourceTopology(zoneInfo v1alpha2.ZoneLi
nrtMutated := nrt.DeepCopy()
nrtMutated.Zones = zoneInfo
updateAttributes(&nrtMutated.Attributes, scanResponse.Attributes)
nrtUpdated, err := cli.TopologyV1alpha2().NodeResourceTopologies().Update(context.TODO(), nrtMutated, metav1.UpdateOptions{})
if err != nil {
@ -261,3 +264,22 @@ func createTopologyAttributes(policy string, scope string) v1alpha2.AttributeLis
},
}
}
func updateAttribute(attrList *v1alpha2.AttributeList, attrInfo v1alpha2.AttributeInfo) {
if attrList == nil {
return
}
for idx := range *attrList {
if (*attrList)[idx].Name == attrInfo.Name {
(*attrList)[idx].Value = attrInfo.Value
return
}
}
*attrList = append(*attrList, attrInfo)
}
func updateAttributes(lhs *v1alpha2.AttributeList, rhs v1alpha2.AttributeList) {
for _, attr := range rhs {
updateAttribute(lhs, attr)
}
}

View file

@ -0,0 +1,112 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package nfdtopologyupdater
import (
"fmt"
"testing"
"github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/apis/topology/v1alpha2"
. "github.com/smartystreets/goconvey/convey"
)
func TestTopologyUpdater(t *testing.T) {
Convey("Given a list of Attributes", t, func() {
attr_two := v1alpha2.AttributeInfo{
Name: "attr_two_name",
Value: "attr_two_value",
}
attrList := v1alpha2.AttributeList{
v1alpha2.AttributeInfo{
Name: "attr_one_name",
Value: "attr_one_value",
},
attr_two,
v1alpha2.AttributeInfo{
Name: "attr_three_name",
Value: "attr_three_value",
},
}
attrListLen := len(attrList)
attrNames := getListOfNames(attrList)
Convey("When an existing attribute is updated", func() {
updatedAttribute := v1alpha2.AttributeInfo{
Name: attr_two.Name,
Value: attr_two.Value + "_new",
}
updateAttribute(&attrList, updatedAttribute)
Convey("Then list should have the same number of elements", func() {
So(attrList, ShouldHaveLength, attrListLen)
})
Convey("Then the order of the elemens should be the same", func() {
So(attrNames, ShouldResemble, getListOfNames(attrList))
})
Convey("Then Attribute value in the list should be updated", func() {
attr, err := findAttributeByName(attrList, attr_two.Name)
So(err, ShouldBeNil)
So(attr.Value, ShouldEqual, updatedAttribute.Value)
})
})
Convey("When a non existing attribute is updated", func() {
completelyNewAttribute := v1alpha2.AttributeInfo{
Name: "NonExistingAttribute_Name",
Value: "NonExistingAttribute_Value",
}
_, err := findAttributeByName(attrList, completelyNewAttribute.Name)
So(err, ShouldNotBeNil)
updateAttribute(&attrList, completelyNewAttribute)
Convey("Then list should have the one more element", func() {
So(attrList, ShouldHaveLength, attrListLen+1)
})
Convey("Then new Attribute should be added at the end of the list", func() {
So(attrList[len(attrList)-1], ShouldResemble, completelyNewAttribute)
})
Convey("Then the order of the elemens should be the same", func() {
So(attrNames, ShouldResemble, getListOfNames(attrList[:len(attrList)-1]))
})
})
})
}
func getListOfNames(attrList v1alpha2.AttributeList) []string {
ret := make([]string, len(attrList))
for idx, attr := range attrList {
ret[idx] = attr.Name
}
return ret
}
func findAttributeByName(attrList v1alpha2.AttributeList, name string) (v1alpha2.AttributeInfo, error) {
for _, attr := range attrList {
if attr.Name == name {
return attr, nil
}
}
return v1alpha2.AttributeInfo{}, fmt.Errorf("Attribute Not Found name:=%s", name)
}

View file

@ -27,20 +27,25 @@ import (
podresourcesapi "k8s.io/kubelet/pkg/apis/podresources/v1"
"sigs.k8s.io/node-feature-discovery/pkg/apihelper"
"github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/apis/topology/v1alpha2"
"github.com/k8stopologyawareschedwg/podfingerprint"
)
type PodResourcesScanner struct {
namespace string
podResourceClient podresourcesapi.PodResourcesListerClient
apihelper apihelper.APIHelpers
podFingerprint bool
}
// NewPodResourcesScanner creates a new ResourcesScanner instance
func NewPodResourcesScanner(namespace string, podResourceClient podresourcesapi.PodResourcesListerClient, kubeApihelper apihelper.APIHelpers) (ResourcesScanner, error) {
func NewPodResourcesScanner(namespace string, podResourceClient podresourcesapi.PodResourcesListerClient, kubeApihelper apihelper.APIHelpers, podFingerprint bool) (ResourcesScanner, error) {
resourcemonitorInstance := &PodResourcesScanner{
namespace: namespace,
podResourceClient: podResourceClient,
apihelper: kubeApihelper,
podFingerprint: podFingerprint,
}
if resourcemonitorInstance.namespace != "*" {
klog.Infof("watching namespace %q", resourcemonitorInstance.namespace)
@ -113,24 +118,43 @@ func hasIntegralCPUs(pod *corev1.Pod, container *corev1.Container) bool {
}
// Scan gathers all the PodResources from the system, using the podresources API client.
func (resMon *PodResourcesScanner) Scan() ([]PodResources, error) {
func (resMon *PodResourcesScanner) Scan() (ScanResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultPodResourcesTimeout)
defer cancel()
// Pod Resource API client
resp, err := resMon.podResourceClient.List(ctx, &podresourcesapi.ListPodResourcesRequest{})
if err != nil {
return nil, fmt.Errorf("can't receive response: %v.Get(_) = _, %w", resMon.podResourceClient, err)
return ScanResponse{}, fmt.Errorf("can't receive response: %v.Get(_) = _, %w", resMon.podResourceClient, err)
}
respPodResources := resp.GetPodResources()
retVal := ScanResponse{
Attributes: v1alpha2.AttributeList{},
}
if resMon.podFingerprint && len(respPodResources) > 0 {
var status podfingerprint.Status
podFingerprintSign, err := computePodFingerprint(respPodResources, &status)
if err != nil {
klog.Errorf("podFingerprint: Unable to compute fingerprint %v", err)
} else {
klog.Info("podFingerprint: " + status.Repr())
retVal.Attributes = append(retVal.Attributes, v1alpha2.AttributeInfo{
Name: podfingerprint.Attribute,
Value: podFingerprintSign,
})
}
}
var podResData []PodResources
for _, podResource := range resp.GetPodResources() {
for _, podResource := range respPodResources {
klog.Infof("podresource iter: %s", podResource.GetName())
hasDevice := hasDevice(podResource)
isWatchable, isIntegralGuaranteed, err := resMon.isWatchable(podResource.GetNamespace(), podResource.GetName(), hasDevice)
if err != nil {
return nil, fmt.Errorf("checking if pod in a namespace is watchable, namespace:%v, pod name %v: %v", podResource.GetNamespace(), podResource.GetName(), err)
return ScanResponse{}, fmt.Errorf("checking if pod in a namespace is watchable, namespace:%v, pod name %v: %v", podResource.GetNamespace(), podResource.GetName(), err)
}
if !isWatchable {
continue
@ -198,7 +222,9 @@ func (resMon *PodResourcesScanner) Scan() ([]PodResources, error) {
}
return podResData, nil
retVal.PodResources = podResData
return retVal, nil
}
func hasDevice(podResource *podresourcesapi.PodResources) bool {
@ -225,3 +251,14 @@ func getNumaNodeIds(topologyInfo *podresourcesapi.TopologyInfo) []int {
return topology
}
func computePodFingerprint(podResources []*podresourcesapi.PodResources, status *podfingerprint.Status) (string, error) {
fingerprint := podfingerprint.NewTracingFingerprint(len(podResources), status)
for _, podResource := range podResources {
err := fingerprint.Add(podResource.Namespace, podResource.Name)
if err != nil {
return "", err
}
}
return fingerprint.Sign(), nil
}

View file

@ -22,6 +22,7 @@ import (
"sort"
"testing"
"github.com/k8stopologyawareschedwg/podfingerprint"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/mock"
@ -40,11 +41,24 @@ func TestPodScanner(t *testing.T) {
var resScan ResourcesScanner
var err error
// PodFingerprint only depends on Name/Namespace of the pods running on a Node
// so we can precalculate the expected value
expectedFingerprintCompute := func(pods []*corev1.Pod) (string, error) {
pf := podfingerprint.NewFingerprint(len(pods))
for _, pr := range pods {
if err := pf.Add(pr.Namespace, pr.Name); err != nil {
return "", err
}
}
return pf.Sign(), nil
}
Convey("When I scan for pod resources using fake client and no namespace", t, func() {
mockPodResClient := new(podres.MockPodResourcesListerClient)
mockAPIHelper := new(apihelper.MockAPIHelpers)
mockClient := &k8sclient.Clientset{}
resScan, err = NewPodResourcesScanner("*", mockPodResClient, mockAPIHelper)
computePodFingerprint := true
resScan, err = NewPodResourcesScanner("*", mockPodResClient, mockAPIHelper, computePodFingerprint)
Convey("Creating a Resources Scanner using a mock client", func() {
So(err, ShouldBeNil)
@ -58,7 +72,10 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldNotBeNil)
})
Convey("Return PodResources should be nil", func() {
So(res, ShouldBeNil)
So(res.PodResources, ShouldBeNil)
})
Convey("Return Attributes should be empty", func() {
So(res.Attributes, ShouldBeEmpty)
})
})
@ -70,7 +87,10 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Return PodResources should be zero", func() {
So(len(res), ShouldEqual, 0)
So(len(res.PodResources), ShouldEqual, 0)
})
Convey("Return Attributes should be empty", func() {
So(res.Attributes, ShouldBeEmpty)
})
})
@ -160,7 +180,7 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
So(len(res.PodResources), ShouldBeGreaterThan, 0)
expected := []PodResources{
{
@ -194,14 +214,22 @@ func TestPodScanner(t *testing.T) {
},
},
}
for _, podresource := range res {
for _, podresource := range res.PodResources {
for _, container := range podresource.Containers {
sort.Slice(res, func(i, j int) bool {
sort.Slice(res.PodResources, func(i, j int) bool {
return container.Resources[i].Name < container.Resources[j].Name
})
}
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
So(reflect.DeepEqual(res.PodResources, expected), ShouldBeTrue)
})
Convey("Return Attributes should have pod fingerprint attribute with proper value", func() {
So(len(res.Attributes), ShouldEqual, 1)
// can compute expected fringerprint only with the list of pods in the node.
expectedFingerprint, err := expectedFingerprintCompute([]*corev1.Pod{pod})
So(err, ShouldBeNil)
So(res.Attributes[0].Name, ShouldEqual, podfingerprint.Attribute)
So(res.Attributes[0].Value, ShouldEqual, expectedFingerprint)
})
})
@ -266,7 +294,7 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
So(len(res.PodResources), ShouldBeGreaterThan, 0)
expected := []PodResources{
{
@ -290,7 +318,15 @@ func TestPodScanner(t *testing.T) {
},
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
So(reflect.DeepEqual(res.PodResources, expected), ShouldBeTrue)
})
Convey("Return Attributes should have pod fingerprint attribute with proper value", func() {
So(len(res.Attributes), ShouldEqual, 1)
// can compute expected fringerprint only with the list of pods in the node.
expectedFingerprint, err := expectedFingerprintCompute([]*corev1.Pod{pod})
So(err, ShouldBeNil)
So(res.Attributes[0].Name, ShouldEqual, podfingerprint.Attribute)
So(res.Attributes[0].Value, ShouldEqual, expectedFingerprint)
})
})
@ -345,7 +381,7 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
So(len(res.PodResources), ShouldBeGreaterThan, 0)
expected := []PodResources{
{
@ -365,7 +401,15 @@ func TestPodScanner(t *testing.T) {
},
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
So(reflect.DeepEqual(res.PodResources, expected), ShouldBeTrue)
})
Convey("Return Attributes should have pod fingerprint attribute with proper value", func() {
So(len(res.Attributes), ShouldEqual, 1)
// can compute expected fringerprint only with the list of pods in the node.
expectedFingerprint, err := expectedFingerprintCompute([]*corev1.Pod{pod})
So(err, ShouldBeNil)
So(res.Attributes[0].Name, ShouldEqual, podfingerprint.Attribute)
So(res.Attributes[0].Value, ShouldEqual, expectedFingerprint)
})
})
@ -427,7 +471,7 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
So(len(res.PodResources), ShouldBeGreaterThan, 0)
expected := []PodResources{
{
@ -447,7 +491,7 @@ func TestPodScanner(t *testing.T) {
},
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
So(reflect.DeepEqual(res.PodResources, expected), ShouldBeTrue)
})
})
@ -505,7 +549,15 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
So(len(res.PodResources), ShouldBeGreaterThan, 0)
})
Convey("Return Attributes should have pod fingerprint attribute with proper value", func() {
So(len(res.Attributes), ShouldEqual, 1)
// can compute expected fringerprint only with the list of pods in the node.
expectedFingerprint, err := expectedFingerprintCompute([]*corev1.Pod{pod})
So(err, ShouldBeNil)
So(res.Attributes[0].Name, ShouldEqual, podfingerprint.Attribute)
So(res.Attributes[0].Value, ShouldEqual, expectedFingerprint)
})
expected := []PodResources{
@ -526,7 +578,7 @@ func TestPodScanner(t *testing.T) {
},
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
So(reflect.DeepEqual(res.PodResources, expected), ShouldBeTrue)
})
Convey("When I successfully get valid response for (non-guaranteed) pods with devices with cpus", func() {
@ -589,7 +641,7 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
So(len(res.PodResources), ShouldBeGreaterThan, 0)
})
expected := []PodResources{
@ -609,16 +661,26 @@ func TestPodScanner(t *testing.T) {
},
},
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
})
So(reflect.DeepEqual(res.PodResources, expected), ShouldBeTrue)
Convey("Return Attributes should have pod fingerprint attribute with proper value", func() {
So(len(res.Attributes), ShouldEqual, 1)
// can compute expected fringerprint only with the list of pods in the node.
expectedFingerprint, err := expectedFingerprintCompute([]*corev1.Pod{pod})
So(err, ShouldBeNil)
So(res.Attributes[0].Name, ShouldEqual, podfingerprint.Attribute)
So(res.Attributes[0].Value, ShouldEqual, expectedFingerprint)
})
})
})
Convey("When I scan for pod resources using fake client and given namespace", t, func() {
mockPodResClient := new(podres.MockPodResourcesListerClient)
mockAPIHelper := new(apihelper.MockAPIHelpers)
mockClient := &k8sclient.Clientset{}
resScan, err = NewPodResourcesScanner("pod-res-test", mockPodResClient, mockAPIHelper)
computePodFingerprint := false
resScan, err = NewPodResourcesScanner("pod-res-test", mockPodResClient, mockAPIHelper, computePodFingerprint)
Convey("Creating a Resources Scanner using a mock client", func() {
So(err, ShouldBeNil)
@ -632,7 +694,7 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldNotBeNil)
})
Convey("Return PodResources should be nil", func() {
So(res, ShouldBeNil)
So(res.PodResources, ShouldBeNil)
})
})
@ -644,7 +706,7 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Return PodResources should be zero", func() {
So(len(res), ShouldEqual, 0)
So(len(res.PodResources), ShouldEqual, 0)
})
})
@ -712,7 +774,7 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Return PodResources should be zero", func() {
So(len(res), ShouldEqual, 0)
So(len(res.PodResources), ShouldEqual, 0)
})
})
@ -778,7 +840,7 @@ func TestPodScanner(t *testing.T) {
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
So(len(res.PodResources), ShouldBeGreaterThan, 0)
expected := []PodResources{
{
@ -802,7 +864,7 @@ func TestPodScanner(t *testing.T) {
},
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
So(reflect.DeepEqual(res.PodResources, expected), ShouldBeTrue)
})
})
@ -858,7 +920,7 @@ func TestPodScanner(t *testing.T) {
})
Convey("Return PodResources should have values", func() {
Convey("Return PodResources should be zero", func() {
So(len(res), ShouldEqual, 0)
So(len(res.PodResources), ShouldEqual, 0)
})
})
})
@ -923,7 +985,7 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldEqual, 0)
So(len(res.PodResources), ShouldEqual, 0)
})
})
@ -981,7 +1043,7 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
So(len(res.PodResources), ShouldBeGreaterThan, 0)
})
expected := []PodResources{
@ -1002,7 +1064,7 @@ func TestPodScanner(t *testing.T) {
},
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
So(reflect.DeepEqual(res.PodResources, expected), ShouldBeTrue)
})
Convey("When I successfully get valid response for (non-guaranteed) pods with devices with cpus", func() {
@ -1065,7 +1127,7 @@ func TestPodScanner(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Return PodResources should have values", func() {
So(len(res), ShouldBeGreaterThan, 0)
So(len(res.PodResources), ShouldBeGreaterThan, 0)
})
expected := []PodResources{
@ -1085,7 +1147,7 @@ func TestPodScanner(t *testing.T) {
},
},
}
So(reflect.DeepEqual(res, expected), ShouldBeTrue)
So(reflect.DeepEqual(res.PodResources, expected), ShouldBeTrue)
})
})

View file

@ -31,6 +31,7 @@ type Args struct {
Namespace string
KubeletConfigURI string
APIAuthTokenFile string
PodSetFingerprint bool
}
// ResourceInfo stores information of resources and their corresponding IDs obtained from PodResource API
@ -53,9 +54,14 @@ type PodResources struct {
Containers []ContainerResources
}
type ScanResponse struct {
PodResources []PodResources
Attributes topologyv1alpha2.AttributeList
}
// ResourcesScanner gathers all the PodResources from the system, using the podresources API client
type ResourcesScanner interface {
Scan() ([]PodResources, error)
Scan() (ScanResponse, error)
}
// ResourcesAggregator aggregates resource information based on the received data from underlying hardware and podresource API

View file

@ -29,6 +29,7 @@ import (
"github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/apis/topology/v1alpha2"
topologyclientset "github.com/k8stopologyawareschedwg/noderesourcetopology-api/pkg/generated/clientset/versioned"
"github.com/k8stopologyawareschedwg/podfingerprint"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
@ -304,8 +305,74 @@ excludeList:
}, 1*time.Minute, 10*time.Second).Should(BeFalse())
})
})
When("topology-updater configure to compute pod fingerprint", func() {
BeforeEach(func() {
cfg, err := testutils.GetConfig()
Expect(err).ToNot(HaveOccurred())
kcfg := cfg.GetKubeletConfig()
By(fmt.Sprintf("Using config (%#v)", kcfg))
podSpecOpts := []testpod.SpecOption{
testpod.SpecWithContainerImage(dockerImage()),
testpod.SpecWithContainerExtraArgs("-pods-fingerprint"),
}
topologyUpdaterDaemonSet = testds.NFDTopologyUpdater(kcfg, podSpecOpts...)
})
It("noderesourcetopology should advertise pod fingerprint in top-level attribute", func() {
Eventually(func() bool {
// get node topology
nodeTopology := testutils.GetNodeTopology(topologyClient, topologyUpdaterNode.Name)
// look for attribute
podFingerprintAttribute, err := findAttribute(nodeTopology.Attributes, podfingerprint.Attribute)
if err != nil {
framework.Logf("podFingerprint attributte %q not found: %v", podfingerprint.Attribute, err)
return false
}
// get pods in node
pods, err := f.ClientSet.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{FieldSelector: "spec.nodeName=" + topologyUpdaterNode.Name})
if err != nil {
framework.Logf("podFingerprint error while recovering %q node pods: %v", topologyUpdaterNode.Name, err)
return false
}
if len(pods.Items) == 0 {
framework.Logf("podFingerprint No pods in node %q", topologyUpdaterNode.Name)
return false
}
// compute expected value
pf := podfingerprint.NewFingerprint(len(pods.Items))
for _, pod := range pods.Items {
err = pf.Add(pod.Namespace, pod.Name)
if err != nil {
framework.Logf("error while computing expected podFingerprint %v", err)
return false
}
}
expectedPodFingerprint := pf.Sign()
if podFingerprintAttribute.Value != expectedPodFingerprint {
framework.Logf("podFingerprint attributte error expected: %q actual: %q", expectedPodFingerprint, podFingerprintAttribute.Value)
return false
}
return true
}, 1*time.Minute, 10*time.Second).Should(BeTrue())
})
})
})
func findAttribute(attributes v1alpha2.AttributeList, attributeName string) (v1alpha2.AttributeInfo, error) {
for _, attrInfo := range attributes {
if attrInfo.Name == attributeName {
return attrInfo, nil
}
}
return v1alpha2.AttributeInfo{}, fmt.Errorf("attribute %q not found", attributeName)
}
// lessAllocatableResources specialize CompareAllocatableResources for this specific e2e use case.
func lessAllocatableResources(expected, got map[string]corev1.ResourceList) (string, string, bool) {
zoneName, resName, cmp, ok := testutils.CompareAllocatableResources(expected, got)