2019-02-08 19:43:54 +00:00
/ *
2021-02-19 05:38:55 +00:00
Copyright 2019 - 2021 The Kubernetes Authors .
2019-02-08 19:43:54 +00:00
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 nfdmaster
import (
2022-11-06 20:25:04 +00:00
"fmt"
2023-03-05 21:56:46 +00:00
"os"
"path/filepath"
2019-05-07 09:41:20 +00:00
"regexp"
"sort"
2019-02-08 19:43:54 +00:00
"strings"
"testing"
2023-03-05 21:56:46 +00:00
"time"
2019-02-08 19:43:54 +00:00
2020-08-13 14:14:27 +00:00
"github.com/smartystreets/assertions"
2019-02-08 19:43:54 +00:00
. "github.com/smartystreets/goconvey/convey"
2020-03-05 14:40:55 +00:00
"github.com/stretchr/testify/mock"
2019-02-08 19:43:54 +00:00
"github.com/vektra/errors"
"golang.org/x/net/context"
2022-10-14 12:28:52 +00:00
corev1 "k8s.io/api/core/v1"
2020-03-05 14:40:55 +00:00
"k8s.io/apimachinery/pkg/api/resource"
2019-02-12 13:31:59 +00:00
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2023-05-13 19:03:53 +00:00
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2019-02-08 19:43:54 +00:00
k8sclient "k8s.io/client-go/kubernetes"
2023-05-13 19:03:53 +00:00
"k8s.io/client-go/tools/cache"
2019-02-08 19:43:54 +00:00
"sigs.k8s.io/node-feature-discovery/pkg/apihelper"
2023-05-24 10:56:36 +00:00
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
2022-08-16 18:39:11 +00:00
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
2023-05-13 19:03:53 +00:00
"sigs.k8s.io/node-feature-discovery/pkg/generated/clientset/versioned/fake"
nfdscheme "sigs.k8s.io/node-feature-discovery/pkg/generated/clientset/versioned/scheme"
nfdinformers "sigs.k8s.io/node-feature-discovery/pkg/generated/informers/externalversions"
2019-02-08 19:43:54 +00:00
"sigs.k8s.io/node-feature-discovery/pkg/labeler"
2021-02-19 05:38:55 +00:00
"sigs.k8s.io/node-feature-discovery/pkg/utils"
2019-02-08 19:43:54 +00:00
"sigs.k8s.io/node-feature-discovery/pkg/version"
2020-08-13 14:14:27 +00:00
"sigs.k8s.io/yaml"
2019-02-08 19:43:54 +00:00
)
const (
mockNodeName = "mock-node"
)
2022-10-14 12:28:52 +00:00
func newMockNode ( ) * corev1 . Node {
n := corev1 . Node { }
2020-03-05 14:40:55 +00:00
n . Name = mockNodeName
2019-02-12 13:31:59 +00:00
n . Labels = map [ string ] string { }
n . Annotations = map [ string ] string { }
2022-10-14 12:28:52 +00:00
n . Status . Capacity = corev1 . ResourceList { }
2019-02-12 13:31:59 +00:00
return & n
}
2023-05-13 19:03:53 +00:00
func mockNodeList ( ) * corev1 . NodeList {
l := corev1 . NodeList { }
for i := 0 ; i < 1000 ; i ++ {
n := corev1 . Node { }
n . Name = fmt . Sprintf ( "node %v" , i )
n . Labels = map [ string ] string { }
n . Annotations = map [ string ] string { }
n . Status . Capacity = corev1 . ResourceList { }
l . Items = append ( l . Items , n )
}
return & l
}
func newMockNfdAPIController ( client * fake . Clientset ) * nfdController {
c := & nfdController {
stopChan : make ( chan struct { } , 1 ) ,
updateAllNodesChan : make ( chan struct { } , 1 ) ,
updateOneNodeChan : make ( chan string ) ,
}
informerFactory := nfdinformers . NewSharedInformerFactory ( client , 1 * time . Hour )
// Add informer for NodeFeature objects
featureInformer := informerFactory . Nfd ( ) . V1alpha1 ( ) . NodeFeatures ( )
if _ , err := featureInformer . Informer ( ) . AddEventHandler ( cache . ResourceEventHandlerFuncs {
AddFunc : func ( obj interface { } ) { } ,
UpdateFunc : func ( oldObj , newObj interface { } ) { } ,
DeleteFunc : func ( obj interface { } ) { } ,
} ) ; err != nil {
return nil
}
c . featureLister = featureInformer . Lister ( )
// Add informer for NodeFeatureRule objects
ruleInformer := informerFactory . Nfd ( ) . V1alpha1 ( ) . NodeFeatureRules ( )
if _ , err := ruleInformer . Informer ( ) . AddEventHandler ( cache . ResourceEventHandlerFuncs {
AddFunc : func ( object interface { } ) { } ,
UpdateFunc : func ( oldObject , newObject interface { } ) { } ,
DeleteFunc : func ( object interface { } ) { } ,
} ) ; err != nil {
return nil
}
c . ruleLister = ruleInformer . Lister ( )
// Start informers
informerFactory . Start ( c . stopChan )
utilruntime . Must ( nfdv1alpha1 . AddToScheme ( nfdscheme . Scheme ) )
return c
}
2021-02-03 17:15:00 +00:00
func newMockMaster ( apihelper apihelper . APIHelpers ) * nfdMaster {
return & nfdMaster {
2022-08-16 18:39:11 +00:00
nodeName : mockNodeName ,
2023-03-05 21:56:46 +00:00
config : & NFDConfig { LabelWhiteList : utils . RegexpVal { Regexp : * regexp . MustCompile ( "" ) } } ,
2022-08-16 18:39:11 +00:00
apihelper : apihelper ,
2021-02-03 17:15:00 +00:00
}
}
2022-10-12 10:45:11 +00:00
func TestUpdateNodeObject ( t * testing . T ) {
2019-02-08 19:43:54 +00:00
Convey ( "When I update the node using fake client" , t , func ( ) {
2021-07-06 12:03:50 +00:00
fakeFeatureLabels := map [ string ] string {
2022-08-16 18:39:11 +00:00
nfdv1alpha1 . FeatureLabelNs + "/source-feature.1" : "1" ,
nfdv1alpha1 . FeatureLabelNs + "/source-feature.2" : "2" ,
nfdv1alpha1 . FeatureLabelNs + "/source-feature.3" : "val3" ,
nfdv1alpha1 . ProfileLabelNs + "/profile-a" : "val4" }
2020-08-13 14:14:27 +00:00
fakeAnnotations := map [ string ] string { "my-annotation" : "my-val" }
2022-08-16 18:39:11 +00:00
fakeExtResources := ExtendedResources { nfdv1alpha1 . FeatureLabelNs + "/source-feature.1" : "1" , nfdv1alpha1 . FeatureLabelNs + "/source-feature.2" : "2" }
2020-08-13 14:14:27 +00:00
2019-02-08 19:43:54 +00:00
fakeFeatureLabelNames := make ( [ ] string , 0 , len ( fakeFeatureLabels ) )
2020-05-19 13:18:29 +00:00
for k := range fakeFeatureLabels {
2022-08-16 18:39:11 +00:00
fakeFeatureLabelNames = append ( fakeFeatureLabelNames , strings . TrimPrefix ( k , nfdv1alpha1 . FeatureLabelNs + "/" ) )
2019-02-08 19:43:54 +00:00
}
2019-05-08 08:08:14 +00:00
sort . Strings ( fakeFeatureLabelNames )
2020-08-13 14:14:27 +00:00
fakeExtResourceNames := make ( [ ] string , 0 , len ( fakeExtResources ) )
for k := range fakeExtResources {
2022-08-16 18:39:11 +00:00
fakeExtResourceNames = append ( fakeExtResourceNames , strings . TrimPrefix ( k , nfdv1alpha1 . FeatureLabelNs + "/" ) )
2020-08-13 14:14:27 +00:00
}
sort . Strings ( fakeExtResourceNames )
2023-04-06 18:20:24 +00:00
// Create a list of expected node status patches
statusPatches := [ ] apihelper . JsonPatch { }
for k , v := range fakeExtResources {
statusPatches = append ( statusPatches , apihelper . NewJsonPatch ( "add" , "/status/capacity" , k , v ) )
}
2019-02-08 19:43:54 +00:00
mockAPIHelper := new ( apihelper . MockAPIHelpers )
2021-02-03 17:19:51 +00:00
mockMaster := newMockMaster ( mockAPIHelper )
2019-02-08 19:43:54 +00:00
mockClient := & k8sclient . Clientset { }
2019-02-12 13:31:59 +00:00
// Mock node with old features
mockNode := newMockNode ( )
2022-08-16 18:39:11 +00:00
mockNode . Labels [ nfdv1alpha1 . FeatureLabelNs + "/old-feature" ] = "old-value"
mockNode . Annotations [ nfdv1alpha1 . AnnotationNs + "/feature-labels" ] = "old-feature"
2019-02-08 19:43:54 +00:00
Convey ( "When I successfully update the node with feature labels" , func ( ) {
2020-08-18 15:05:06 +00:00
// Create a list of expected node metadata patches
2020-08-13 14:14:27 +00:00
metadataPatches := [ ] apihelper . JsonPatch {
2022-08-16 18:39:11 +00:00
apihelper . NewJsonPatch ( "replace" , "/metadata/annotations" , nfdv1alpha1 . AnnotationNs + "/feature-labels" , strings . Join ( fakeFeatureLabelNames , "," ) ) ,
apihelper . NewJsonPatch ( "add" , "/metadata/annotations" , nfdv1alpha1 . AnnotationNs + "/extended-resources" , strings . Join ( fakeExtResourceNames , "," ) ) ,
apihelper . NewJsonPatch ( "remove" , "/metadata/labels" , nfdv1alpha1 . FeatureLabelNs + "/old-feature" , "" ) ,
2020-08-13 14:14:27 +00:00
}
for k , v := range fakeFeatureLabels {
metadataPatches = append ( metadataPatches , apihelper . NewJsonPatch ( "add" , "/metadata/labels" , k , v ) )
}
2020-08-18 14:11:52 +00:00
for k , v := range fakeAnnotations {
2020-08-18 15:05:06 +00:00
metadataPatches = append ( metadataPatches , apihelper . NewJsonPatch ( "add" , "/metadata/annotations" , k , v ) )
}
2019-02-08 19:43:54 +00:00
mockAPIHelper . On ( "GetClient" ) . Return ( mockClient , nil )
2022-10-12 10:45:11 +00:00
mockAPIHelper . On ( "GetNode" , mockClient , mockNodeName ) . Return ( mockNode , nil ) . Twice ( )
2020-08-18 15:05:06 +00:00
mockAPIHelper . On ( "PatchNodeStatus" , mockClient , mockNodeName , mock . MatchedBy ( jsonPatchMatcher ( statusPatches ) ) ) . Return ( nil )
2023-04-06 18:20:24 +00:00
mockAPIHelper . On ( "PatchNode" , mockClient , mockNodeName , mock . MatchedBy ( jsonPatchMatcher ( metadataPatches ) ) ) . Return ( nil )
2022-10-12 10:45:11 +00:00
err := mockMaster . updateNodeObject ( mockClient , mockNodeName , fakeFeatureLabels , fakeAnnotations , fakeExtResources , nil )
2019-02-08 19:43:54 +00:00
Convey ( "Error is nil" , func ( ) {
So ( err , ShouldBeNil )
} )
} )
Convey ( "When I fail to update the node with feature labels" , func ( ) {
2022-11-06 20:25:04 +00:00
expectedError := fmt . Errorf ( "no client is passed, client: <nil>" )
2019-02-08 19:43:54 +00:00
mockAPIHelper . On ( "GetClient" ) . Return ( nil , expectedError )
2022-10-12 10:45:11 +00:00
err := mockMaster . updateNodeObject ( nil , mockNodeName , fakeFeatureLabels , fakeAnnotations , fakeExtResources , nil )
2019-02-08 19:43:54 +00:00
Convey ( "Error is produced" , func ( ) {
2022-11-06 20:25:04 +00:00
So ( err , ShouldResemble , expectedError )
2019-02-08 19:43:54 +00:00
} )
} )
Convey ( "When I fail to get a mock client while updating feature labels" , func ( ) {
2022-11-06 20:25:04 +00:00
expectedError := fmt . Errorf ( "no client is passed, client: <nil>" )
2019-02-08 19:43:54 +00:00
mockAPIHelper . On ( "GetClient" ) . Return ( nil , expectedError )
2022-10-12 10:45:11 +00:00
err := mockMaster . updateNodeObject ( nil , mockNodeName , fakeFeatureLabels , fakeAnnotations , fakeExtResources , nil )
2019-02-08 19:43:54 +00:00
Convey ( "Error is produced" , func ( ) {
2022-11-06 20:25:04 +00:00
So ( err , ShouldResemble , expectedError )
2019-02-08 19:43:54 +00:00
} )
} )
Convey ( "When I fail to get a mock node while updating feature labels" , func ( ) {
expectedError := errors . New ( "fake error" )
mockAPIHelper . On ( "GetClient" ) . Return ( mockClient , nil )
2022-10-12 10:45:11 +00:00
mockAPIHelper . On ( "GetNode" , mockClient , mockNodeName ) . Return ( nil , expectedError ) . Twice ( )
err := mockMaster . updateNodeObject ( mockClient , mockNodeName , fakeFeatureLabels , fakeAnnotations , fakeExtResources , nil )
2019-02-08 19:43:54 +00:00
Convey ( "Error is produced" , func ( ) {
So ( err , ShouldEqual , expectedError )
} )
} )
Convey ( "When I fail to update a mock node while updating feature labels" , func ( ) {
expectedError := errors . New ( "fake error" )
mockAPIHelper . On ( "GetClient" ) . Return ( mockClient , nil )
2022-10-12 10:45:11 +00:00
mockAPIHelper . On ( "GetNode" , mockClient , mockNodeName ) . Return ( mockNode , nil ) . Twice ( )
2023-04-06 18:20:24 +00:00
mockAPIHelper . On ( "PatchNodeStatus" , mockClient , mockNodeName , mock . MatchedBy ( jsonPatchMatcher ( statusPatches ) ) ) . Return ( nil )
2022-10-12 10:45:11 +00:00
mockAPIHelper . On ( "PatchNode" , mockClient , mockNodeName , mock . Anything ) . Return ( expectedError ) . Twice ( )
err := mockMaster . updateNodeObject ( mockClient , mockNodeName , fakeFeatureLabels , fakeAnnotations , fakeExtResources , nil )
2019-02-08 19:43:54 +00:00
Convey ( "Error is produced" , func ( ) {
2021-02-19 09:03:16 +00:00
So ( err . Error ( ) , ShouldEndWith , expectedError . Error ( ) )
2019-02-08 19:43:54 +00:00
} )
} )
} )
}
func TestUpdateMasterNode ( t * testing . T ) {
Convey ( "When updating the nfd-master node" , t , func ( ) {
mockHelper := & apihelper . MockAPIHelpers { }
2021-02-03 17:15:00 +00:00
mockMaster := newMockMaster ( mockHelper )
2019-02-08 19:43:54 +00:00
mockClient := & k8sclient . Clientset { }
2019-02-12 13:31:59 +00:00
mockNode := newMockNode ( )
2019-02-08 19:43:54 +00:00
Convey ( "When update operation succeeds" , func ( ) {
2020-08-13 14:14:27 +00:00
expectedPatches := [ ] apihelper . JsonPatch {
2022-08-16 18:39:11 +00:00
apihelper . NewJsonPatch ( "add" , "/metadata/annotations" , nfdv1alpha1 . AnnotationNs + "/master.version" , version . Get ( ) ) }
2019-02-08 19:43:54 +00:00
mockHelper . On ( "GetClient" ) . Return ( mockClient , nil )
mockHelper . On ( "GetNode" , mockClient , mockNodeName ) . Return ( mockNode , nil )
2020-08-13 14:14:27 +00:00
mockHelper . On ( "PatchNode" , mockClient , mockNodeName , mock . MatchedBy ( jsonPatchMatcher ( expectedPatches ) ) ) . Return ( nil )
2021-02-03 17:15:00 +00:00
err := mockMaster . updateMasterNode ( )
2019-02-08 19:43:54 +00:00
Convey ( "No error should be returned" , func ( ) {
So ( err , ShouldBeNil )
} )
} )
2021-02-19 09:03:16 +00:00
mockErr := errors . New ( "failed to patch node annotations: mock-error'" )
2019-02-08 19:43:54 +00:00
Convey ( "When getting API client fails" , func ( ) {
mockHelper . On ( "GetClient" ) . Return ( mockClient , mockErr )
2021-02-03 17:15:00 +00:00
err := mockMaster . updateMasterNode ( )
2019-02-08 19:43:54 +00:00
Convey ( "An error should be returned" , func ( ) {
So ( err , ShouldEqual , mockErr )
} )
} )
Convey ( "When getting API node object fails" , func ( ) {
mockHelper . On ( "GetClient" ) . Return ( mockClient , nil )
mockHelper . On ( "GetNode" , mockClient , mockNodeName ) . Return ( mockNode , mockErr )
2021-02-03 17:15:00 +00:00
err := mockMaster . updateMasterNode ( )
2019-02-08 19:43:54 +00:00
Convey ( "An error should be returned" , func ( ) {
So ( err , ShouldEqual , mockErr )
} )
} )
Convey ( "When updating node object fails" , func ( ) {
mockHelper . On ( "GetClient" ) . Return ( mockClient , nil )
mockHelper . On ( "GetNode" , mockClient , mockNodeName ) . Return ( mockNode , nil )
2020-08-13 14:14:27 +00:00
mockHelper . On ( "PatchNode" , mockClient , mockNodeName , mock . Anything ) . Return ( mockErr )
2021-02-03 17:15:00 +00:00
err := mockMaster . updateMasterNode ( )
2019-02-08 19:43:54 +00:00
Convey ( "An error should be returned" , func ( ) {
2021-02-19 09:03:16 +00:00
So ( err . Error ( ) , ShouldEndWith , mockErr . Error ( ) )
2019-02-08 19:43:54 +00:00
} )
} )
} )
}
2020-03-05 14:40:55 +00:00
func TestAddingExtResources ( t * testing . T ) {
Convey ( "When adding extended resources" , t , func ( ) {
2021-02-03 17:49:02 +00:00
mockMaster := newMockMaster ( nil )
2020-03-05 14:40:55 +00:00
Convey ( "When there are no matching labels" , func ( ) {
mockNode := newMockNode ( )
mockResourceLabels := ExtendedResources { }
2021-02-03 17:49:02 +00:00
patches := mockMaster . createExtendedResourcePatches ( mockNode , mockResourceLabels )
2020-08-13 14:14:27 +00:00
So ( len ( patches ) , ShouldEqual , 0 )
2020-03-05 14:40:55 +00:00
} )
Convey ( "When there are matching labels" , func ( ) {
mockNode := newMockNode ( )
2020-08-18 15:05:06 +00:00
mockResourceLabels := ExtendedResources { "feature-1" : "1" , "feature-2" : "2" }
expectedPatches := [ ] apihelper . JsonPatch {
apihelper . NewJsonPatch ( "add" , "/status/capacity" , "feature-1" , "1" ) ,
apihelper . NewJsonPatch ( "add" , "/status/capacity" , "feature-2" , "2" ) ,
}
2021-02-03 17:49:02 +00:00
patches := mockMaster . createExtendedResourcePatches ( mockNode , mockResourceLabels )
2020-08-18 15:05:06 +00:00
So ( sortJsonPatches ( patches ) , ShouldResemble , sortJsonPatches ( expectedPatches ) )
2020-03-05 14:40:55 +00:00
} )
Convey ( "When the resource already exists" , func ( ) {
mockNode := newMockNode ( )
2022-10-14 12:28:52 +00:00
mockNode . Status . Capacity [ corev1 . ResourceName ( nfdv1alpha1 . FeatureLabelNs + "/feature-1" ) ] = * resource . NewQuantity ( 1 , resource . BinarySI )
2022-08-16 18:39:11 +00:00
mockResourceLabels := ExtendedResources { nfdv1alpha1 . FeatureLabelNs + "/feature-1" : "1" }
2021-02-03 17:49:02 +00:00
patches := mockMaster . createExtendedResourcePatches ( mockNode , mockResourceLabels )
2020-08-13 14:14:27 +00:00
So ( len ( patches ) , ShouldEqual , 0 )
2020-03-05 14:40:55 +00:00
} )
Convey ( "When the resource already exists but its capacity has changed" , func ( ) {
mockNode := newMockNode ( )
2022-10-14 12:28:52 +00:00
mockNode . Status . Capacity [ corev1 . ResourceName ( "feature-1" ) ] = * resource . NewQuantity ( 2 , resource . BinarySI )
2020-08-18 15:05:06 +00:00
mockResourceLabels := ExtendedResources { "feature-1" : "1" }
expectedPatches := [ ] apihelper . JsonPatch {
apihelper . NewJsonPatch ( "replace" , "/status/capacity" , "feature-1" , "1" ) ,
apihelper . NewJsonPatch ( "replace" , "/status/allocatable" , "feature-1" , "1" ) ,
}
2021-02-03 17:49:02 +00:00
patches := mockMaster . createExtendedResourcePatches ( mockNode , mockResourceLabels )
2020-08-18 15:05:06 +00:00
So ( sortJsonPatches ( patches ) , ShouldResemble , sortJsonPatches ( expectedPatches ) )
2020-03-05 14:40:55 +00:00
} )
} )
}
func TestRemovingExtResources ( t * testing . T ) {
Convey ( "When removing extended resources" , t , func ( ) {
2021-02-03 17:49:02 +00:00
mockMaster := newMockMaster ( nil )
2020-03-05 14:40:55 +00:00
Convey ( "When none are removed" , func ( ) {
mockNode := newMockNode ( )
2022-08-16 18:39:11 +00:00
mockResourceLabels := ExtendedResources { nfdv1alpha1 . FeatureLabelNs + "/feature-1" : "1" , nfdv1alpha1 . FeatureLabelNs + "/feature-2" : "2" }
mockNode . Annotations [ nfdv1alpha1 . AnnotationNs + "/extended-resources" ] = "feature-1,feature-2"
2022-10-14 12:28:52 +00:00
mockNode . Status . Capacity [ corev1 . ResourceName ( nfdv1alpha1 . FeatureLabelNs + "/feature-1" ) ] = * resource . NewQuantity ( 1 , resource . BinarySI )
mockNode . Status . Capacity [ corev1 . ResourceName ( nfdv1alpha1 . FeatureLabelNs + "/feature-2" ) ] = * resource . NewQuantity ( 2 , resource . BinarySI )
2021-02-03 17:49:02 +00:00
patches := mockMaster . createExtendedResourcePatches ( mockNode , mockResourceLabels )
2020-08-13 14:14:27 +00:00
So ( len ( patches ) , ShouldEqual , 0 )
2020-03-05 14:40:55 +00:00
} )
Convey ( "When the related label is gone" , func ( ) {
mockNode := newMockNode ( )
2022-08-16 18:39:11 +00:00
mockResourceLabels := ExtendedResources { nfdv1alpha1 . FeatureLabelNs + "/feature-4" : "" , nfdv1alpha1 . FeatureLabelNs + "/feature-2" : "2" }
mockNode . Annotations [ nfdv1alpha1 . AnnotationNs + "/extended-resources" ] = "feature-4,feature-2"
2022-10-14 12:28:52 +00:00
mockNode . Status . Capacity [ corev1 . ResourceName ( nfdv1alpha1 . FeatureLabelNs + "/feature-4" ) ] = * resource . NewQuantity ( 4 , resource . BinarySI )
mockNode . Status . Capacity [ corev1 . ResourceName ( nfdv1alpha1 . FeatureLabelNs + "/feature-2" ) ] = * resource . NewQuantity ( 2 , resource . BinarySI )
2021-02-03 17:49:02 +00:00
patches := mockMaster . createExtendedResourcePatches ( mockNode , mockResourceLabels )
2020-08-13 14:14:27 +00:00
So ( len ( patches ) , ShouldBeGreaterThan , 0 )
2020-03-05 14:40:55 +00:00
} )
Convey ( "When the extended resource is no longer wanted" , func ( ) {
mockNode := newMockNode ( )
2022-10-14 12:28:52 +00:00
mockNode . Status . Capacity [ corev1 . ResourceName ( nfdv1alpha1 . FeatureLabelNs + "/feature-1" ) ] = * resource . NewQuantity ( 1 , resource . BinarySI )
mockNode . Status . Capacity [ corev1 . ResourceName ( nfdv1alpha1 . FeatureLabelNs + "/feature-2" ) ] = * resource . NewQuantity ( 2 , resource . BinarySI )
2022-08-16 18:39:11 +00:00
mockResourceLabels := ExtendedResources { nfdv1alpha1 . FeatureLabelNs + "/feature-2" : "2" }
mockNode . Annotations [ nfdv1alpha1 . AnnotationNs + "/extended-resources" ] = "feature-1,feature-2"
2021-02-03 17:49:02 +00:00
patches := mockMaster . createExtendedResourcePatches ( mockNode , mockResourceLabels )
2020-08-13 14:14:27 +00:00
So ( len ( patches ) , ShouldBeGreaterThan , 0 )
2020-03-05 14:40:55 +00:00
} )
} )
}
2019-02-08 19:43:54 +00:00
func TestSetLabels ( t * testing . T ) {
Convey ( "When servicing SetLabels request" , t , func ( ) {
2022-10-12 10:45:11 +00:00
const workerName = mockNodeName
2019-02-08 19:43:54 +00:00
const workerVer = "0.1-test"
mockHelper := & apihelper . MockAPIHelpers { }
2021-02-03 17:15:00 +00:00
mockMaster := newMockMaster ( mockHelper )
2019-02-08 19:43:54 +00:00
mockClient := & k8sclient . Clientset { }
2019-02-12 13:31:59 +00:00
mockNode := newMockNode ( )
2019-02-08 19:43:54 +00:00
mockCtx := context . Background ( )
2020-08-12 18:28:21 +00:00
// In the gRPC request the label names may omit the default ns
2020-08-18 15:05:06 +00:00
mockLabels := map [ string ] string { "feature-1" : "1" , "feature-2" : "val-2" , "feature-3" : "3" }
2019-05-07 09:41:20 +00:00
mockReq := & labeler . SetLabelsRequest { NodeName : workerName , NfdVersion : workerVer , Labels : mockLabels }
mockLabelNames := make ( [ ] string , 0 , len ( mockLabels ) )
for k := range mockLabels {
mockLabelNames = append ( mockLabelNames , k )
}
sort . Strings ( mockLabelNames )
2019-02-08 19:43:54 +00:00
2020-08-18 15:05:06 +00:00
expectedStatusPatches := [ ] apihelper . JsonPatch { }
2019-02-08 19:43:54 +00:00
Convey ( "When node update succeeds" , func ( ) {
2020-08-13 14:14:27 +00:00
expectedPatches := [ ] apihelper . JsonPatch {
2022-08-16 18:39:11 +00:00
apihelper . NewJsonPatch ( "add" , "/metadata/annotations" , nfdv1alpha1 . WorkerVersionAnnotation , workerVer ) ,
apihelper . NewJsonPatch ( "add" , "/metadata/annotations" , nfdv1alpha1 . FeatureLabelsAnnotation , strings . Join ( mockLabelNames , "," ) ) ,
2020-08-13 14:14:27 +00:00
}
for k , v := range mockLabels {
2022-08-16 18:39:11 +00:00
expectedPatches = append ( expectedPatches , apihelper . NewJsonPatch ( "add" , "/metadata/labels" , nfdv1alpha1 . FeatureLabelNs + "/" + k , v ) )
2020-08-13 14:14:27 +00:00
}
2019-02-08 19:43:54 +00:00
mockHelper . On ( "GetClient" ) . Return ( mockClient , nil )
2022-10-12 10:45:11 +00:00
mockHelper . On ( "GetNode" , mockClient , workerName ) . Return ( mockNode , nil ) . Twice ( )
2020-08-18 15:05:06 +00:00
mockHelper . On ( "PatchNodeStatus" , mockClient , mockNodeName , mock . MatchedBy ( jsonPatchMatcher ( expectedStatusPatches ) ) ) . Return ( nil )
2023-04-06 18:20:24 +00:00
mockHelper . On ( "PatchNode" , mockClient , mockNodeName , mock . MatchedBy ( jsonPatchMatcher ( expectedPatches ) ) ) . Return ( nil )
2021-02-03 17:00:02 +00:00
_ , err := mockMaster . SetLabels ( mockCtx , mockReq )
2019-02-08 19:43:54 +00:00
Convey ( "No error should be returned" , func ( ) {
So ( err , ShouldBeNil )
} )
2019-05-07 09:41:20 +00:00
} )
2021-11-25 10:14:19 +00:00
Convey ( "When -label-whitelist is specified" , func ( ) {
2020-08-13 14:14:27 +00:00
expectedPatches := [ ] apihelper . JsonPatch {
2022-08-16 18:39:11 +00:00
apihelper . NewJsonPatch ( "add" , "/metadata/annotations" , nfdv1alpha1 . WorkerVersionAnnotation , workerVer ) ,
apihelper . NewJsonPatch ( "add" , "/metadata/annotations" , nfdv1alpha1 . FeatureLabelsAnnotation , "feature-2" ) ,
apihelper . NewJsonPatch ( "add" , "/metadata/labels" , nfdv1alpha1 . FeatureLabelNs + "/feature-2" , mockLabels [ "feature-2" ] ) ,
2020-08-13 14:14:27 +00:00
}
2023-03-05 21:56:46 +00:00
mockMaster . config . LabelWhiteList . Regexp = * regexp . MustCompile ( "^f.*2$" )
2019-05-07 09:41:20 +00:00
mockHelper . On ( "GetClient" ) . Return ( mockClient , nil )
mockHelper . On ( "GetNode" , mockClient , workerName ) . Return ( mockNode , nil )
2020-08-18 15:05:06 +00:00
mockHelper . On ( "PatchNodeStatus" , mockClient , mockNodeName , mock . MatchedBy ( jsonPatchMatcher ( expectedStatusPatches ) ) ) . Return ( nil )
2023-04-06 18:20:24 +00:00
mockHelper . On ( "PatchNode" , mockClient , mockNodeName , mock . MatchedBy ( jsonPatchMatcher ( expectedPatches ) ) ) . Return ( nil )
2021-02-03 17:00:02 +00:00
_ , err := mockMaster . SetLabels ( mockCtx , mockReq )
2019-05-07 09:41:20 +00:00
Convey ( "Error is nil" , func ( ) {
So ( err , ShouldBeNil )
} )
2019-02-08 19:43:54 +00:00
} )
2023-02-03 19:27:26 +00:00
Convey ( "When -extra-label-ns, -deny-label-ns and -instance are specified" , func ( ) {
2020-08-12 18:28:21 +00:00
// In the gRPC request the label names may omit the default ns
2021-02-03 17:49:02 +00:00
instance := "foo"
2022-08-16 18:39:11 +00:00
vendorFeatureLabel := "vendor." + nfdv1alpha1 . FeatureLabelNs + "/feature-4"
vendorProfileLabel := "vendor." + nfdv1alpha1 . ProfileLabelNs + "/feature-5"
2023-03-10 10:39:41 +00:00
mockLabels := map [ string ] string {
"feature-1" : "val-1" ,
2023-02-03 19:27:26 +00:00
"valid.ns/feature-2" : "val-2" ,
"random.denied.ns/feature-3" : "val-3" ,
"kubernetes.io/feature-4" : "val-4" ,
"sub.ns.kubernetes.io/feature-5" : "val-5" ,
2023-05-26 13:06:56 +00:00
vendorFeatureLabel : "val-6" ,
vendorProfileLabel : "val-7" ,
"--invalid-name--" : "valid-val" ,
"valid-name" : "--invalid-val--" }
2020-08-13 14:14:27 +00:00
expectedPatches := [ ] apihelper . JsonPatch {
2022-08-16 18:39:11 +00:00
apihelper . NewJsonPatch ( "add" , "/metadata/annotations" , instance + "." + nfdv1alpha1 . WorkerVersionAnnotation , workerVer ) ,
2021-07-06 12:03:50 +00:00
apihelper . NewJsonPatch ( "add" , "/metadata/annotations" ,
2022-08-16 18:39:11 +00:00
instance + "." + nfdv1alpha1 . FeatureLabelsAnnotation ,
2021-07-06 12:03:50 +00:00
"feature-1,valid.ns/feature-2," + vendorFeatureLabel + "," + vendorProfileLabel ) ,
2022-08-16 18:39:11 +00:00
apihelper . NewJsonPatch ( "add" , "/metadata/labels" , nfdv1alpha1 . FeatureLabelNs + "/feature-1" , mockLabels [ "feature-1" ] ) ,
2020-08-13 14:14:27 +00:00
apihelper . NewJsonPatch ( "add" , "/metadata/labels" , "valid.ns/feature-2" , mockLabels [ "valid.ns/feature-2" ] ) ,
2021-07-06 12:03:50 +00:00
apihelper . NewJsonPatch ( "add" , "/metadata/labels" , vendorFeatureLabel , mockLabels [ vendorFeatureLabel ] ) ,
apihelper . NewJsonPatch ( "add" , "/metadata/labels" , vendorProfileLabel , mockLabels [ vendorProfileLabel ] ) ,
2020-08-13 14:14:27 +00:00
}
2023-02-03 19:27:26 +00:00
mockMaster . deniedNs . normal = map [ string ] struct { } { "random.denied.ns" : { } }
mockMaster . deniedNs . wildcard = map [ string ] struct { } { "kubernetes.io" : { } }
2023-03-05 21:56:46 +00:00
mockMaster . config . ExtraLabelNs = map [ string ] struct { } { "valid.ns" : { } }
2022-08-16 18:39:11 +00:00
mockMaster . args . Instance = instance
2020-08-13 14:14:27 +00:00
mockHelper . On ( "GetClient" ) . Return ( mockClient , nil )
mockHelper . On ( "GetNode" , mockClient , workerName ) . Return ( mockNode , nil )
2020-08-18 15:05:06 +00:00
mockHelper . On ( "PatchNodeStatus" , mockClient , mockNodeName , mock . MatchedBy ( jsonPatchMatcher ( expectedStatusPatches ) ) ) . Return ( nil )
2023-04-06 18:20:24 +00:00
mockHelper . On ( "PatchNode" , mockClient , mockNodeName , mock . MatchedBy ( jsonPatchMatcher ( expectedPatches ) ) ) . Return ( nil )
2019-04-05 22:31:40 +00:00
mockReq := & labeler . SetLabelsRequest { NodeName : workerName , NfdVersion : workerVer , Labels : mockLabels }
2021-02-03 17:00:02 +00:00
_ , err := mockMaster . SetLabels ( mockCtx , mockReq )
2019-04-05 22:31:40 +00:00
Convey ( "Error is nil" , func ( ) {
So ( err , ShouldBeNil )
} )
2022-08-16 18:39:11 +00:00
mockMaster . args . Instance = ""
2020-08-18 15:05:06 +00:00
} )
2021-11-25 10:14:19 +00:00
Convey ( "When -resource-labels is specified" , func ( ) {
2020-08-18 15:05:06 +00:00
expectedPatches := [ ] apihelper . JsonPatch {
2022-08-16 18:39:11 +00:00
apihelper . NewJsonPatch ( "add" , "/metadata/annotations" , nfdv1alpha1 . WorkerVersionAnnotation , workerVer ) ,
apihelper . NewJsonPatch ( "add" , "/metadata/annotations" , nfdv1alpha1 . FeatureLabelsAnnotation , "feature-2" ) ,
apihelper . NewJsonPatch ( "add" , "/metadata/annotations" , nfdv1alpha1 . ExtendedResourceAnnotation , "feature-1,feature-3" ) ,
apihelper . NewJsonPatch ( "add" , "/metadata/labels" , nfdv1alpha1 . FeatureLabelNs + "/feature-2" , mockLabels [ "feature-2" ] ) ,
2020-08-18 15:05:06 +00:00
}
expectedStatusPatches := [ ] apihelper . JsonPatch {
2022-08-16 18:39:11 +00:00
apihelper . NewJsonPatch ( "add" , "/status/capacity" , nfdv1alpha1 . FeatureLabelNs + "/feature-1" , mockLabels [ "feature-1" ] ) ,
apihelper . NewJsonPatch ( "add" , "/status/capacity" , nfdv1alpha1 . FeatureLabelNs + "/feature-3" , mockLabels [ "feature-3" ] ) ,
2020-08-18 15:05:06 +00:00
}
2023-03-05 21:56:46 +00:00
mockMaster . config . ResourceLabels = map [ string ] struct { } { "feature-3" : { } , "feature-1" : { } }
2020-08-18 15:05:06 +00:00
mockHelper . On ( "GetClient" ) . Return ( mockClient , nil )
mockHelper . On ( "GetNode" , mockClient , workerName ) . Return ( mockNode , nil )
mockHelper . On ( "PatchNodeStatus" , mockClient , mockNodeName , mock . MatchedBy ( jsonPatchMatcher ( expectedStatusPatches ) ) ) . Return ( nil )
2023-04-06 18:20:24 +00:00
mockHelper . On ( "PatchNode" , mockClient , mockNodeName , mock . MatchedBy ( jsonPatchMatcher ( expectedPatches ) ) ) . Return ( nil )
2021-02-03 17:00:02 +00:00
_ , err := mockMaster . SetLabels ( mockCtx , mockReq )
2020-08-18 15:05:06 +00:00
Convey ( "Error is nil" , func ( ) {
So ( err , ShouldBeNil )
} )
2019-04-05 22:31:40 +00:00
} )
2019-02-08 19:43:54 +00:00
mockErr := errors . New ( "mock-error" )
Convey ( "When node update fails" , func ( ) {
mockHelper . On ( "GetClient" ) . Return ( mockClient , mockErr )
2021-02-03 17:00:02 +00:00
_ , err := mockMaster . SetLabels ( mockCtx , mockReq )
2019-02-08 19:43:54 +00:00
Convey ( "An error should be returned" , func ( ) {
So ( err , ShouldEqual , mockErr )
} )
} )
2023-03-05 21:56:46 +00:00
mockMaster . config . NoPublish = true
2021-11-25 10:14:19 +00:00
Convey ( "With '-no-publish'" , func ( ) {
2021-02-03 17:00:02 +00:00
_ , err := mockMaster . SetLabels ( mockCtx , mockReq )
2019-02-08 19:43:54 +00:00
Convey ( "Operation should succeed" , func ( ) {
So ( err , ShouldBeNil )
} )
} )
} )
}
2019-02-12 13:31:59 +00:00
2023-05-24 10:56:36 +00:00
func TestFilterLabels ( t * testing . T ) {
mockHelper := & apihelper . MockAPIHelpers { }
mockMaster := newMockMaster ( mockHelper )
Convey ( "When using dynamic values" , t , func ( ) {
labelName := "testLabel"
labelValue := "@test.feature.LSM"
features := nfdv1alpha1 . Features {
Attributes : map [ string ] nfdv1alpha1 . AttributeFeatureSet {
"test.feature" : v1alpha1 . AttributeFeatureSet {
Elements : map [ string ] string {
"LSM" : "123" ,
} ,
} ,
} ,
}
labelValue , err := mockMaster . filterFeatureLabel ( labelName , labelValue , & features )
Convey ( "Operation should succeed" , func ( ) {
So ( err , ShouldBeNil )
} )
Convey ( "Label value should change" , func ( ) {
So ( labelValue , ShouldEqual , "123" )
} )
} )
}
2020-08-13 14:14:27 +00:00
func TestCreatePatches ( t * testing . T ) {
Convey ( "When creating JSON patches" , t , func ( ) {
existingItems := map [ string ] string { "key-1" : "val-1" , "key-2" : "val-2" , "key-3" : "val-3" }
jsonPath := "/root"
2019-02-12 13:31:59 +00:00
2020-08-13 14:14:27 +00:00
Convey ( "When when there are neither itmes to remoe nor to add or update" , func ( ) {
p := createPatches ( [ ] string { "foo" , "bar" } , existingItems , map [ string ] string { } , jsonPath )
So ( len ( p ) , ShouldEqual , 0 )
} )
2019-02-12 13:31:59 +00:00
2020-08-13 14:14:27 +00:00
Convey ( "When when there are itmes to remoe but none to add or update" , func ( ) {
p := createPatches ( [ ] string { "key-2" , "key-3" , "foo" } , existingItems , map [ string ] string { } , jsonPath )
expected := [ ] apihelper . JsonPatch {
apihelper . NewJsonPatch ( "remove" , jsonPath , "key-2" , "" ) ,
apihelper . NewJsonPatch ( "remove" , jsonPath , "key-3" , "" ) ,
}
So ( sortJsonPatches ( p ) , ShouldResemble , sortJsonPatches ( expected ) )
2019-02-12 13:31:59 +00:00
} )
2020-08-13 14:14:27 +00:00
Convey ( "When when there are no itmes to remove but new items to add" , func ( ) {
newItems := map [ string ] string { "new-key" : "new-val" , "key-1" : "new-1" }
p := createPatches ( [ ] string { "key-1" } , existingItems , newItems , jsonPath )
expected := [ ] apihelper . JsonPatch {
apihelper . NewJsonPatch ( "add" , jsonPath , "new-key" , newItems [ "new-key" ] ) ,
apihelper . NewJsonPatch ( "replace" , jsonPath , "key-1" , newItems [ "key-1" ] ) ,
}
So ( sortJsonPatches ( p ) , ShouldResemble , sortJsonPatches ( expected ) )
} )
Convey ( "When when there are items to remove add and update" , func ( ) {
newItems := map [ string ] string { "new-key" : "new-val" , "key-2" : "new-2" , "key-4" : "val-4" }
p := createPatches ( [ ] string { "key-1" , "key-2" , "key-3" , "foo" } , existingItems , newItems , jsonPath )
expected := [ ] apihelper . JsonPatch {
apihelper . NewJsonPatch ( "add" , jsonPath , "new-key" , newItems [ "new-key" ] ) ,
apihelper . NewJsonPatch ( "add" , jsonPath , "key-4" , newItems [ "key-4" ] ) ,
apihelper . NewJsonPatch ( "replace" , jsonPath , "key-2" , newItems [ "key-2" ] ) ,
apihelper . NewJsonPatch ( "remove" , jsonPath , "key-1" , "" ) ,
apihelper . NewJsonPatch ( "remove" , jsonPath , "key-3" , "" ) ,
}
So ( sortJsonPatches ( p ) , ShouldResemble , sortJsonPatches ( expected ) )
2019-02-12 13:31:59 +00:00
} )
} )
}
func TestRemoveLabelsWithPrefix ( t * testing . T ) {
Convey ( "When removing labels" , t , func ( ) {
2022-10-14 12:28:52 +00:00
n := & corev1 . Node {
2019-02-12 13:31:59 +00:00
ObjectMeta : meta_v1 . ObjectMeta {
Labels : map [ string ] string {
"single-label" : "123" ,
"multiple_A" : "a" ,
"multiple_B" : "b" ,
} ,
} ,
}
Convey ( "a unique label should be removed" , func ( ) {
2020-08-13 14:14:27 +00:00
p := removeLabelsWithPrefix ( n , "single" )
So ( p , ShouldResemble , [ ] apihelper . JsonPatch { apihelper . NewJsonPatch ( "remove" , "/metadata/labels" , "single-label" , "" ) } )
2019-02-12 13:31:59 +00:00
} )
Convey ( "a non-unique search string should remove all matching keys" , func ( ) {
2020-08-13 14:14:27 +00:00
p := removeLabelsWithPrefix ( n , "multiple" )
So ( sortJsonPatches ( p ) , ShouldResemble , sortJsonPatches ( [ ] apihelper . JsonPatch {
apihelper . NewJsonPatch ( "remove" , "/metadata/labels" , "multiple_A" , "" ) ,
apihelper . NewJsonPatch ( "remove" , "/metadata/labels" , "multiple_B" , "" ) ,
} ) )
2019-02-12 13:31:59 +00:00
} )
Convey ( "a search string with no matches should not alter labels" , func ( ) {
removeLabelsWithPrefix ( n , "unique" )
So ( n . Labels , ShouldContainKey , "single-label" )
So ( n . Labels , ShouldContainKey , "multiple_A" )
So ( n . Labels , ShouldContainKey , "multiple_B" )
So ( len ( n . Labels ) , ShouldEqual , 3 )
} )
} )
}
2020-08-13 14:14:27 +00:00
2023-03-05 21:56:46 +00:00
func TestConfigParse ( t * testing . T ) {
Convey ( "When parsing configuration" , t , func ( ) {
m , err := NewNfdMaster ( & Args { } )
So ( err , ShouldBeNil )
master := m . ( * nfdMaster )
overrides := ` { "noPublish": true, "enableTaints": true, "extraLabelNs": ["added.ns.io","added.kubernetes.io"], "denyLabelNs": ["denied.ns.io","denied.kubernetes.io"], "resourceLabels": ["vendor-1.com/feature-1","vendor-2.io/feature-2"], "labelWhiteList": "foo"} `
Convey ( "and no core cmdline flags have been specified" , func ( ) {
So ( master . configure ( "non-existing-file" , overrides ) , ShouldBeNil )
Convey ( "overrides should be in effect" , func ( ) {
So ( master . config . NoPublish , ShouldResemble , true )
So ( master . config . EnableTaints , ShouldResemble , true )
So ( master . config . ExtraLabelNs , ShouldResemble , utils . StringSetVal { "added.ns.io" : struct { } { } , "added.kubernetes.io" : struct { } { } } )
So ( master . config . DenyLabelNs , ShouldResemble , utils . StringSetVal { "denied.ns.io" : struct { } { } , "denied.kubernetes.io" : struct { } { } } )
So ( master . config . ResourceLabels , ShouldResemble , utils . StringSetVal { "vendor-1.com/feature-1" : struct { } { } , "vendor-2.io/feature-2" : struct { } { } } )
So ( master . config . LabelWhiteList . String ( ) , ShouldEqual , "foo" )
} )
} )
Convey ( "and a non-accessible file, but cmdline flags and some overrides are specified" , func ( ) {
master . args = Args { Overrides : ConfigOverrideArgs {
ExtraLabelNs : & utils . StringSetVal { "override.added.ns.io" : struct { } { } } ,
DenyLabelNs : & utils . StringSetVal { "override.denied.ns.io" : struct { } { } } } }
So ( master . configure ( "non-existing-file" , overrides ) , ShouldBeNil )
Convey ( "cmdline flags should be in effect instead overrides" , func ( ) {
So ( master . config . ExtraLabelNs , ShouldResemble , utils . StringSetVal { "override.added.ns.io" : struct { } { } } )
So ( master . config . DenyLabelNs , ShouldResemble , utils . StringSetVal { "override.denied.ns.io" : struct { } { } } )
} )
Convey ( "overrides should take effect" , func ( ) {
So ( master . config . NoPublish , ShouldBeTrue )
So ( master . config . EnableTaints , ShouldBeTrue )
} )
} )
// Create a temporary config file
f , err := os . CreateTemp ( "" , "nfd-test-" )
defer os . Remove ( f . Name ( ) )
So ( err , ShouldBeNil )
_ , err = f . WriteString ( `
noPublish : true
denyLabelNs : [ "denied.ns.io" , "denied.kubernetes.io" ]
resourceLabels : [ "vendor-1.com/feature-1" , "vendor-2.io/feature-2" ]
enableTaints : false
labelWhiteList : "foo"
2023-05-05 10:01:32 +00:00
leaderElection :
leaseDuration : 20 s
renewDeadline : 4 s
retryPeriod : 30 s
2023-03-05 21:56:46 +00:00
` )
f . Close ( )
So ( err , ShouldBeNil )
Convey ( "and a proper config file is specified" , func ( ) {
master . args = Args { Overrides : ConfigOverrideArgs { ExtraLabelNs : & utils . StringSetVal { "override.added.ns.io" : struct { } { } } } }
So ( master . configure ( f . Name ( ) , "" ) , ShouldBeNil )
Convey ( "specified configuration should take effect" , func ( ) {
// Verify core config
So ( master . config . NoPublish , ShouldBeTrue )
So ( master . config . EnableTaints , ShouldBeFalse )
So ( master . config . ExtraLabelNs , ShouldResemble , utils . StringSetVal { "override.added.ns.io" : struct { } { } } )
So ( master . config . ResourceLabels , ShouldResemble , utils . StringSetVal { "vendor-1.com/feature-1" : struct { } { } , "vendor-2.io/feature-2" : struct { } { } } ) // from cmdline
So ( master . config . DenyLabelNs , ShouldResemble , utils . StringSetVal { "denied.ns.io" : struct { } { } , "denied.kubernetes.io" : struct { } { } } )
So ( master . config . LabelWhiteList . String ( ) , ShouldEqual , "foo" )
2023-05-05 10:01:32 +00:00
So ( master . config . LeaderElection . LeaseDuration . Seconds ( ) , ShouldEqual , float64 ( 20 ) )
So ( master . config . LeaderElection . RenewDeadline . Seconds ( ) , ShouldEqual , float64 ( 4 ) )
So ( master . config . LeaderElection . RetryPeriod . Seconds ( ) , ShouldEqual , float64 ( 30 ) )
2023-03-05 21:56:46 +00:00
} )
} )
Convey ( "and a proper config file and overrides are given" , func ( ) {
master . args = Args { Overrides : ConfigOverrideArgs { DenyLabelNs : & utils . StringSetVal { "denied.ns.io" : struct { } { } } } }
overrides := ` { "extraLabelNs": ["added.ns.io"], "noPublish": true} `
So ( master . configure ( f . Name ( ) , overrides ) , ShouldBeNil )
Convey ( "overrides should take precedence over the config file" , func ( ) {
// Verify core config
So ( master . config . ExtraLabelNs , ShouldResemble , utils . StringSetVal { "added.ns.io" : struct { } { } } ) // from overrides
So ( master . config . DenyLabelNs , ShouldResemble , utils . StringSetVal { "denied.ns.io" : struct { } { } } ) // from cmdline
} )
} )
} )
}
func TestDynamicConfig ( t * testing . T ) {
Convey ( "When running nfd-master" , t , func ( ) {
tmpDir , err := os . MkdirTemp ( "" , "*.nfd-test" )
So ( err , ShouldBeNil )
defer os . RemoveAll ( tmpDir )
// Create (temporary) dir for config
configDir := filepath . Join ( tmpDir , "subdir-1" , "subdir-2" , "master.conf" )
err = os . MkdirAll ( configDir , 0755 )
So ( err , ShouldBeNil )
// Create config file
configFile := filepath . Join ( configDir , "master.conf" )
writeConfig := func ( data string ) {
f , err := os . Create ( configFile )
So ( err , ShouldBeNil )
_ , err = f . WriteString ( data )
So ( err , ShouldBeNil )
err = f . Close ( )
So ( err , ShouldBeNil )
}
writeConfig ( `
extraLabelNs : [ "added.ns.io" ]
` )
noPublish := true
m , err := NewNfdMaster ( & Args {
ConfigFile : configFile ,
Overrides : ConfigOverrideArgs {
NoPublish : & noPublish ,
} ,
} )
So ( err , ShouldBeNil )
master := m . ( * nfdMaster )
Convey ( "config file updates should take effect" , func ( ) {
go func ( ) { _ = m . Run ( ) } ( )
defer m . Stop ( )
// Check initial config
time . Sleep ( 10 * time . Second )
So ( func ( ) interface { } { return master . config . ExtraLabelNs } ,
withTimeout , 2 * time . Second , ShouldResemble , utils . StringSetVal { "added.ns.io" : struct { } { } } )
// Update config and verify the effect
writeConfig ( `
extraLabelNs : [ "override.ns.io" ]
2023-04-15 15:11:59 +00:00
resyncPeriod : ' 2 h '
2023-05-13 19:03:53 +00:00
nfdApiParallelism : 300
2023-03-05 21:56:46 +00:00
` )
So ( func ( ) interface { } { return master . config . ExtraLabelNs } ,
withTimeout , 2 * time . Second , ShouldResemble , utils . StringSetVal { "override.ns.io" : struct { } { } } )
2023-04-15 15:11:59 +00:00
So ( func ( ) interface { } { return master . config . ResyncPeriod . Duration } ,
withTimeout , 2 * time . Second , ShouldResemble , time . Duration ( 2 ) * time . Hour )
2023-05-13 19:03:53 +00:00
So ( func ( ) interface { } { return master . config . NfdApiParallelism } ,
withTimeout , 2 * time . Second , ShouldResemble , 300 )
2023-03-05 21:56:46 +00:00
// Removing config file should get back our defaults
err = os . RemoveAll ( tmpDir )
So ( err , ShouldBeNil )
So ( func ( ) interface { } { return master . config . ExtraLabelNs } ,
withTimeout , 2 * time . Second , ShouldResemble , utils . StringSetVal { } )
2023-04-15 15:11:59 +00:00
So ( func ( ) interface { } { return master . config . ResyncPeriod . Duration } ,
withTimeout , 2 * time . Second , ShouldResemble , time . Duration ( 1 ) * time . Hour )
2023-05-13 19:03:53 +00:00
So ( func ( ) interface { } { return master . config . NfdApiParallelism } ,
withTimeout , 2 * time . Second , ShouldResemble , 10 )
2023-03-05 21:56:46 +00:00
// Re-creating config dir and file should change the config
err = os . MkdirAll ( configDir , 0755 )
So ( err , ShouldBeNil )
writeConfig ( `
extraLabelNs : [ "another.override.ns" ]
2023-04-15 15:11:59 +00:00
resyncPeriod : ' 3 m '
2023-05-13 19:03:53 +00:00
nfdApiParallelism : 100
2023-03-05 21:56:46 +00:00
` )
So ( func ( ) interface { } { return master . config . ExtraLabelNs } ,
withTimeout , 2 * time . Second , ShouldResemble , utils . StringSetVal { "another.override.ns" : struct { } { } } )
2023-04-15 15:11:59 +00:00
So ( func ( ) interface { } { return master . config . ResyncPeriod . Duration } ,
withTimeout , 2 * time . Second , ShouldResemble , time . Duration ( 3 ) * time . Minute )
2023-05-13 19:03:53 +00:00
So ( func ( ) interface { } { return master . config . NfdApiParallelism } ,
withTimeout , 2 * time . Second , ShouldResemble , 100 )
2023-03-05 21:56:46 +00:00
} )
} )
}
2023-05-13 19:03:53 +00:00
func BenchmarkNfdAPIUpdateAllNodes ( b * testing . B ) {
mockAPIHelper := new ( apihelper . MockAPIHelpers )
mockMaster := newMockMaster ( mockAPIHelper )
mockMaster . nfdController = newMockNfdAPIController ( fake . NewSimpleClientset ( ) )
mockMaster . config . NoPublish = true
mockNodeUpdaterPool := newNodeUpdaterPool ( mockMaster )
mockMaster . nodeUpdaterPool = mockNodeUpdaterPool
mockClient := & k8sclient . Clientset { }
statusPatches := [ ] apihelper . JsonPatch { }
metadataPatches := [ ] apihelper . JsonPatch {
{ Op : "add" , Path : "/metadata/annotations/nfd.node.kubernetes.io~1feature-labels" , Value : "" } , { Op : "add" , Path : "/metadata/annotations/nfd.node.kubernetes.io~1extended-resources" , Value : "" } ,
}
mockAPIHelper . On ( "GetClient" ) . Return ( mockClient , nil )
mockAPIHelper . On ( "GetNodes" , mockClient ) . Return ( mockNodeList ( ) , nil )
mockNodeUpdaterPool . start ( 10 )
for i := 0 ; i < 1000 ; i ++ {
nodeName := fmt . Sprintf ( "node %v" , i )
node := corev1 . Node { }
node . Name = nodeName
mockAPIHelper . On ( "GetNode" , mockClient , nodeName ) . Return ( & node , nil )
mockAPIHelper . On ( "PatchNodeStatus" , mockClient , nodeName , mock . MatchedBy ( jsonPatchMatcher ( statusPatches ) ) ) . Return ( nil )
mockAPIHelper . On ( "PatchNode" , mockClient , nodeName , mock . MatchedBy ( jsonPatchMatcher ( metadataPatches ) ) ) . Return ( nil )
}
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
_ = mockMaster . nfdAPIUpdateAllNodes ( )
}
fmt . Println ( b . Elapsed ( ) )
}
2023-03-05 21:56:46 +00:00
// withTimeout is a custom assertion for polling a value asynchronously
// actual is a function for getting the actual value
// expected[0] is a time.Duration value specifying the timeout
// expected[1] is the "real" assertion function to be called
// expected[2:] are the arguments for the "real" assertion function
func withTimeout ( actual interface { } , expected ... interface { } ) string {
getter , ok := actual . ( func ( ) interface { } )
if ! ok {
return "not getterFunc"
}
t , ok := expected [ 0 ] . ( time . Duration )
if ! ok {
return "not time.Duration"
}
f , ok := expected [ 1 ] . ( func ( interface { } , ... interface { } ) string )
if ! ok {
return "not an assert func"
}
timeout := time . After ( t )
for {
result := f ( getter ( ) , expected [ 2 : ] ... )
if result == "" {
return ""
}
select {
case <- timeout :
return result
case <- time . After ( 10 * time . Millisecond ) :
}
}
}
2020-08-13 14:14:27 +00:00
func jsonPatchMatcher ( expected [ ] apihelper . JsonPatch ) func ( [ ] apihelper . JsonPatch ) bool {
return func ( actual [ ] apihelper . JsonPatch ) bool {
// We don't care about modifying the original slices
ok , msg := assertions . So ( sortJsonPatches ( actual ) , ShouldResemble , sortJsonPatches ( expected ) )
if ! ok {
// We parse the cryptic string message for better readability
var f assertions . FailureView
if err := yaml . Unmarshal ( [ ] byte ( msg ) , & f ) ; err == nil {
2021-10-04 06:51:38 +00:00
_ , _ = Printf ( "%s\n" , f . Message )
2020-08-13 14:14:27 +00:00
} else {
2021-10-04 06:51:38 +00:00
_ , _ = Printf ( "%s\n" , msg )
2020-08-13 14:14:27 +00:00
}
}
return ok
}
}
func sortJsonPatches ( p [ ] apihelper . JsonPatch ) [ ] apihelper . JsonPatch {
sort . Slice ( p , func ( i , j int ) bool { return p [ i ] . Path < p [ j ] . Path } )
return p
}
2022-09-20 16:56:58 +00:00
// Remove any labels having the given prefix
2022-10-14 12:28:52 +00:00
func removeLabelsWithPrefix ( n * corev1 . Node , search string ) [ ] apihelper . JsonPatch {
2022-09-20 16:56:58 +00:00
var p [ ] apihelper . JsonPatch
for k := range n . Labels {
if strings . HasPrefix ( k , search ) {
p = append ( p , apihelper . NewJsonPatch ( "remove" , "/metadata/labels" , k , "" ) )
}
}
return p
}