github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/overcommit/node/matcher/matcher_test.go (about)

     1  /*
     2  Copyright 2022 The Katalyst Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package matcher
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	v1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	v12 "k8s.io/client-go/listers/core/v1"
    29  	"k8s.io/client-go/tools/cache"
    30  
    31  	"github.com/kubewharf/katalyst-api/pkg/apis/overcommit/v1alpha1"
    32  	v1alpha12 "github.com/kubewharf/katalyst-api/pkg/client/listers/overcommit/v1alpha1"
    33  	"github.com/kubewharf/katalyst-api/pkg/consts"
    34  )
    35  
    36  func fakeIndexer() cache.Indexer {
    37  	return cache.NewIndexer(func(obj interface{}) (string, error) {
    38  		noc, ok := obj.(*v1alpha1.NodeOvercommitConfig)
    39  		if !ok {
    40  			return "", nil
    41  		}
    42  		return noc.Name, nil
    43  	}, cache.Indexers{
    44  		LabelSelectorValIndex: func(obj interface{}) ([]string, error) {
    45  			noc, ok := obj.(*v1alpha1.NodeOvercommitConfig)
    46  			if !ok {
    47  				return []string{}, nil
    48  			}
    49  			return []string{noc.Spec.NodeOvercommitSelectorVal}, nil
    50  		},
    51  	})
    52  }
    53  
    54  func fakeNodeIndexer() cache.Indexer {
    55  	return cache.NewIndexer(func(obj interface{}) (string, error) {
    56  		node, ok := obj.(*v1.Node)
    57  		if !ok {
    58  			return "", nil
    59  		}
    60  		return node.Name, nil
    61  	}, cache.Indexers{
    62  		"test": func(obj interface{}) ([]string, error) {
    63  			return []string{}, nil
    64  		},
    65  	})
    66  }
    67  
    68  func makeTestMatcher() *MatcherImpl {
    69  	nodeIndexer := testNodeIndexer()
    70  	indexer := testNocIndexer()
    71  	nocLister := v1alpha12.NewNodeOvercommitConfigLister(indexer)
    72  	nodeLister := v12.NewNodeLister(nodeIndexer)
    73  
    74  	return NewMatcher(nodeLister, nocLister, indexer)
    75  }
    76  
    77  func makeInitedMatcher() (*MatcherImpl, error) {
    78  	m := makeTestMatcher()
    79  	err := m.Reconcile()
    80  	return m, err
    81  }
    82  
    83  func testNodeIndexer() cache.Indexer {
    84  	indexer := fakeNodeIndexer()
    85  
    86  	indexer.Add(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node1", UID: "01", Labels: map[string]string{consts.NodeOvercommitSelectorKey: "pool1"}}})
    87  	indexer.Add(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node2", UID: "02", Labels: map[string]string{consts.NodeOvercommitSelectorKey: "pool2"}}})
    88  	indexer.Add(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node3", UID: "03", Labels: map[string]string{consts.NodeOvercommitSelectorKey: "pool1"}}})
    89  	indexer.Add(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node4", UID: "04", Labels: map[string]string{consts.NodeOvercommitSelectorKey: "pool3"}}})
    90  	indexer.Add(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node5", UID: "05"}})
    91  	indexer.Add(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node6", UID: "06", Labels: map[string]string{"pool": "pool1"}}})
    92  	return indexer
    93  }
    94  
    95  func testNocIndexer() cache.Indexer {
    96  	indexer := fakeIndexer()
    97  
    98  	indexer.Add(&v1alpha1.NodeOvercommitConfig{
    99  		ObjectMeta: metav1.ObjectMeta{
   100  			Name: "config1",
   101  		},
   102  		Spec: v1alpha1.NodeOvercommitConfigSpec{
   103  			NodeOvercommitSelectorVal: "pool1",
   104  			ResourceOvercommitRatio: map[v1.ResourceName]string{
   105  				v1.ResourceCPU:    "1.5",
   106  				v1.ResourceMemory: "1",
   107  			},
   108  		},
   109  	})
   110  
   111  	indexer.Add(&v1alpha1.NodeOvercommitConfig{
   112  		ObjectMeta: metav1.ObjectMeta{
   113  			Name: "config2",
   114  		},
   115  		Spec: v1alpha1.NodeOvercommitConfigSpec{
   116  			NodeOvercommitSelectorVal: "pool2",
   117  			ResourceOvercommitRatio: map[v1.ResourceName]string{
   118  				v1.ResourceCPU:    "2",
   119  				v1.ResourceMemory: "1",
   120  			},
   121  		},
   122  	})
   123  
   124  	return indexer
   125  }
   126  
   127  func makeMatcherByIndexer(nodeIndexer, nocIndexer cache.Indexer) *MatcherImpl {
   128  	nodeLister := v12.NewNodeLister(nodeIndexer)
   129  	nocLister := v1alpha12.NewNodeOvercommitConfigLister(nocIndexer)
   130  
   131  	matcher := NewMatcher(nodeLister, nocLister, nocIndexer)
   132  	_ = matcher.Reconcile()
   133  	return matcher
   134  }
   135  
   136  func TestMatchConfigNameToNodes(t *testing.T) {
   137  	t.Parallel()
   138  
   139  	testCases := []struct {
   140  		name       string
   141  		configName string
   142  		result     []string
   143  	}{
   144  		{
   145  			name:       "nodeList",
   146  			configName: "config1",
   147  			result:     []string{"node1", "node3"},
   148  		},
   149  		{
   150  			name:       "default matches all nodes",
   151  			configName: "config2",
   152  			result:     []string{"node2"},
   153  		},
   154  	}
   155  
   156  	matcher := makeTestMatcher()
   157  
   158  	for _, tc := range testCases {
   159  		tc := tc
   160  		t.Run(tc.name, func(t *testing.T) {
   161  			t.Parallel()
   162  
   163  			out, err := matcher.matchConfigNameToNodes(tc.configName)
   164  			assert.Nil(t, err)
   165  			assert.Equal(t, len(tc.result), len(out))
   166  		})
   167  	}
   168  }
   169  
   170  func TestMatchConfig(t *testing.T) {
   171  	t.Parallel()
   172  
   173  	nodeIndexer1 := testNodeIndexer()
   174  	nocIndexer1 := testNocIndexer()
   175  	matcher1 := makeMatcherByIndexer(nodeIndexer1, nocIndexer1)
   176  	nocIndexer1.Update(
   177  		&v1alpha1.NodeOvercommitConfig{
   178  			ObjectMeta: metav1.ObjectMeta{
   179  				Name: "config2",
   180  			},
   181  			Spec: v1alpha1.NodeOvercommitConfigSpec{
   182  				NodeOvercommitSelectorVal: "pool3",
   183  				ResourceOvercommitRatio: map[v1.ResourceName]string{
   184  					v1.ResourceCPU:    "2",
   185  					v1.ResourceMemory: "1",
   186  				},
   187  			},
   188  		})
   189  
   190  	nodeIndexer2 := testNodeIndexer()
   191  	nocIndexer2 := testNocIndexer()
   192  	matcher2 := makeMatcherByIndexer(nodeIndexer2, nocIndexer2)
   193  	nocIndexer2.Add(&v1alpha1.NodeOvercommitConfig{
   194  		ObjectMeta: metav1.ObjectMeta{
   195  			Name: "config3",
   196  		},
   197  		Spec: v1alpha1.NodeOvercommitConfigSpec{
   198  			NodeOvercommitSelectorVal: "pool3",
   199  			ResourceOvercommitRatio: map[v1.ResourceName]string{
   200  				v1.ResourceCPU: "3",
   201  			},
   202  		},
   203  	})
   204  
   205  	nodeIndexer3 := testNodeIndexer()
   206  	nocIndexer3 := testNocIndexer()
   207  	matcher3 := makeMatcherByIndexer(nodeIndexer3, nocIndexer3)
   208  	nocIndexer3.Delete(&v1alpha1.NodeOvercommitConfig{
   209  		ObjectMeta: metav1.ObjectMeta{
   210  			Name: "config1",
   211  		},
   212  		Spec: v1alpha1.NodeOvercommitConfigSpec{
   213  			NodeOvercommitSelectorVal: "pool1",
   214  			ResourceOvercommitRatio: map[v1.ResourceName]string{
   215  				v1.ResourceCPU:    "1.5",
   216  				v1.ResourceMemory: "1",
   217  			},
   218  		},
   219  	})
   220  
   221  	testCases := []struct {
   222  		name       string
   223  		matcher    Matcher
   224  		configName string
   225  		result     []string
   226  	}{
   227  		{
   228  			name:       "update selector",
   229  			matcher:    matcher1,
   230  			configName: "config2",
   231  			result:     []string{"node2", "node4"},
   232  		},
   233  		{
   234  			name:       "add config",
   235  			matcher:    matcher2,
   236  			configName: "config3",
   237  			result:     []string{"node4"},
   238  		},
   239  		{
   240  			name:       "delete config",
   241  			matcher:    matcher3,
   242  			configName: "config1",
   243  			result:     []string{"node1", "node3"},
   244  		},
   245  	}
   246  
   247  	for _, tc := range testCases {
   248  		tc := tc
   249  		t.Run(tc.name, func(t *testing.T) {
   250  			t.Parallel()
   251  
   252  			out, err := tc.matcher.MatchConfig(tc.configName)
   253  			assert.Nil(t, err)
   254  			sort.Strings(out)
   255  			assert.Equal(t, tc.result, out)
   256  		})
   257  	}
   258  }
   259  
   260  func TestMatchNode(t *testing.T) {
   261  	t.Parallel()
   262  
   263  	nodeIndexer1 := testNodeIndexer()
   264  	nocIndexer1 := testNocIndexer()
   265  	matcher1 := makeMatcherByIndexer(nodeIndexer1, nocIndexer1)
   266  	nocIndexer1.Update(&v1alpha1.NodeOvercommitConfig{
   267  		ObjectMeta: metav1.ObjectMeta{
   268  			Name: "config2",
   269  		},
   270  		Spec: v1alpha1.NodeOvercommitConfigSpec{
   271  			NodeOvercommitSelectorVal: "pool3",
   272  			ResourceOvercommitRatio: map[v1.ResourceName]string{
   273  				v1.ResourceCPU:    "2",
   274  				v1.ResourceMemory: "1",
   275  			},
   276  		},
   277  	})
   278  
   279  	nodeIndexer2 := testNodeIndexer()
   280  	nocIndexer2 := testNocIndexer()
   281  	matcher2 := makeMatcherByIndexer(nodeIndexer2, nocIndexer2)
   282  	nocIndexer2.Add(&v1alpha1.NodeOvercommitConfig{
   283  		ObjectMeta: metav1.ObjectMeta{
   284  			Name: "config3",
   285  		},
   286  		Spec: v1alpha1.NodeOvercommitConfigSpec{
   287  			NodeOvercommitSelectorVal: "pool3",
   288  			ResourceOvercommitRatio: map[v1.ResourceName]string{
   289  				v1.ResourceCPU: "3",
   290  			},
   291  		},
   292  	})
   293  
   294  	nodeIndexer3 := testNodeIndexer()
   295  	nocIndexer3 := testNocIndexer()
   296  	matcher3 := makeMatcherByIndexer(nodeIndexer3, nocIndexer3)
   297  	nocIndexer3.Delete(
   298  		&v1alpha1.NodeOvercommitConfig{
   299  			ObjectMeta: metav1.ObjectMeta{
   300  				Name: "config1",
   301  			},
   302  		},
   303  	)
   304  
   305  	nodeIndexer5 := testNodeIndexer()
   306  	nocIndexer5 := testNocIndexer()
   307  	matcher5 := makeMatcherByIndexer(nodeIndexer5, nocIndexer5)
   308  	nodeIndexer5.Update(
   309  		&v1.Node{
   310  			ObjectMeta: metav1.ObjectMeta{
   311  				Name: "node4",
   312  				Labels: map[string]string{
   313  					consts.NodeOvercommitSelectorKey: "pool1",
   314  				},
   315  			},
   316  		})
   317  
   318  	nodeIndexer6 := testNodeIndexer()
   319  	nocIndexer6 := testNocIndexer()
   320  	matcher6 := makeMatcherByIndexer(nodeIndexer6, nocIndexer6)
   321  	nodeIndexer5.Update(
   322  		&v1.Node{
   323  			ObjectMeta: metav1.ObjectMeta{
   324  				Name:   "node1",
   325  				Labels: nil,
   326  			},
   327  		})
   328  
   329  	nodeIndexer7 := testNodeIndexer()
   330  	nocIndexer7 := testNocIndexer()
   331  	matcher7 := makeMatcherByIndexer(nodeIndexer7, nocIndexer7)
   332  	nodeIndexer7.Add(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node7", UID: "07", Labels: map[string]string{consts.NodeOvercommitSelectorKey: "pool1"}}})
   333  
   334  	oldNodeMap := map[string]string{
   335  		"node1": "config1",
   336  		"node2": "config2",
   337  		"node3": "config1",
   338  	}
   339  
   340  	testCases := []struct {
   341  		name       string
   342  		matcher    Matcher
   343  		configName string
   344  		nodeName   string
   345  		oldNodeMap map[string]string
   346  		newNodeMap map[string]string
   347  	}{
   348  		{
   349  			name:       "update selector",
   350  			matcher:    matcher1,
   351  			configName: "config2",
   352  			nodeName:   "",
   353  			oldNodeMap: oldNodeMap,
   354  			newNodeMap: map[string]string{
   355  				"node1": "config1",
   356  				"node3": "config1",
   357  				"node4": "config2",
   358  			},
   359  		},
   360  		{
   361  			name:       "add config",
   362  			matcher:    matcher2,
   363  			configName: "config3",
   364  			nodeName:   "",
   365  			oldNodeMap: oldNodeMap,
   366  			newNodeMap: map[string]string{
   367  				"node1": "config1",
   368  				"node2": "config2",
   369  				"node3": "config1",
   370  				"node4": "config3",
   371  			},
   372  		},
   373  		{
   374  			name:       "delete config",
   375  			matcher:    matcher3,
   376  			configName: "config1",
   377  			nodeName:   "",
   378  			oldNodeMap: oldNodeMap,
   379  			newNodeMap: map[string]string{
   380  				"node2": "config2",
   381  			},
   382  		},
   383  		{
   384  			name:       "update node label",
   385  			matcher:    matcher5,
   386  			configName: "",
   387  			nodeName:   "node4",
   388  			oldNodeMap: oldNodeMap,
   389  			newNodeMap: map[string]string{
   390  				"node1": "config1",
   391  				"node2": "config2",
   392  				"node3": "config1",
   393  				"node4": "config1",
   394  			},
   395  		},
   396  		{
   397  			name:       "update node label2",
   398  			matcher:    matcher6,
   399  			configName: "",
   400  			nodeName:   "node1",
   401  			oldNodeMap: oldNodeMap,
   402  			newNodeMap: map[string]string{
   403  				"node2": "config2",
   404  				"node3": "config1",
   405  			},
   406  		},
   407  		{
   408  			name:       "add node",
   409  			matcher:    matcher7,
   410  			configName: "",
   411  			nodeName:   "node7",
   412  			oldNodeMap: oldNodeMap,
   413  			newNodeMap: map[string]string{
   414  				"node1": "config1",
   415  				"node2": "config2",
   416  				"node3": "config1",
   417  				"node7": "config1",
   418  			},
   419  		},
   420  	}
   421  
   422  	for _, tc := range testCases {
   423  		tc := tc
   424  		t.Run(tc.name, func(t *testing.T) {
   425  			t.Parallel()
   426  
   427  			if tc.configName == "" {
   428  				for _, c := range []string{"config1", "config2"} {
   429  					_, _ = tc.matcher.MatchConfig(c)
   430  				}
   431  				tc.matcher.MatchNode(tc.nodeName)
   432  			} else {
   433  				nodes, _ := tc.matcher.MatchConfig(tc.configName)
   434  				for _, node := range nodes {
   435  					tc.matcher.MatchNode(node)
   436  				}
   437  			}
   438  
   439  			for k, v := range tc.newNodeMap {
   440  				config := tc.matcher.GetConfig(k)
   441  				assert.Equal(t, config.Name, v)
   442  			}
   443  		})
   444  	}
   445  }
   446  
   447  func TestDelNode(t *testing.T) {
   448  	t.Parallel()
   449  
   450  	matcher1, _ := makeInitedMatcher()
   451  	matcher2, _ := makeInitedMatcher()
   452  
   453  	testCases := []struct {
   454  		name     string
   455  		nodeName string
   456  		matcher  *MatcherImpl
   457  		result   int
   458  	}{
   459  		{
   460  			name:     "exist node",
   461  			nodeName: "node1",
   462  			matcher:  matcher1,
   463  			result:   2,
   464  		},
   465  		{
   466  			name:     "non-existing node",
   467  			nodeName: "node99",
   468  			matcher:  matcher2,
   469  			result:   3,
   470  		},
   471  	}
   472  
   473  	for _, tc := range testCases {
   474  		tc := tc
   475  		t.Run(tc.name, func(t *testing.T) {
   476  			t.Parallel()
   477  
   478  			tc.matcher.DelNode(tc.nodeName)
   479  			fmt.Printf("testCase: %v", tc)
   480  			assert.Equal(t, tc.result, len(tc.matcher.nodeToConfig))
   481  		})
   482  	}
   483  }
   484  
   485  func TestSort(t *testing.T) {
   486  	t.Parallel()
   487  
   488  	testCases := []struct {
   489  		name    string
   490  		nocList NocList
   491  		result  string
   492  	}{
   493  		{
   494  			name: "creationTimestamp",
   495  			nocList: NocList{
   496  				&v1alpha1.NodeOvercommitConfig{
   497  					ObjectMeta: metav1.ObjectMeta{
   498  						Name:              "noclist1",
   499  						CreationTimestamp: metav1.NewTime(time.Now().Add(-10 * time.Minute)),
   500  					},
   501  				},
   502  				&v1alpha1.NodeOvercommitConfig{
   503  					ObjectMeta: metav1.ObjectMeta{
   504  						Name:              "noclist2",
   505  						CreationTimestamp: metav1.NewTime(time.Now()),
   506  					},
   507  				},
   508  			},
   509  			result: "noclist2",
   510  		},
   511  	}
   512  
   513  	for _, tc := range testCases {
   514  		tc := tc
   515  		t.Run(tc.name, func(t *testing.T) {
   516  			t.Parallel()
   517  			sort.Sort(tc.nocList)
   518  			assert.Equal(t, tc.result, tc.nocList[0].Name)
   519  		})
   520  	}
   521  }