k8s.io/kubernetes@v1.29.3/pkg/scheduler/internal/cache/snapshot_test.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes 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 cache
    18  
    19  import (
    20  	"fmt"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	v1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/labels"
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  	"k8s.io/kubernetes/pkg/scheduler/framework"
    29  	st "k8s.io/kubernetes/pkg/scheduler/testing"
    30  )
    31  
    32  const mb int64 = 1024 * 1024
    33  
    34  func TestGetNodeImageStates(t *testing.T) {
    35  	tests := []struct {
    36  		node              *v1.Node
    37  		imageExistenceMap map[string]sets.Set[string]
    38  		expected          map[string]*framework.ImageStateSummary
    39  	}{
    40  		{
    41  			node: &v1.Node{
    42  				ObjectMeta: metav1.ObjectMeta{Name: "node-0"},
    43  				Status: v1.NodeStatus{
    44  					Images: []v1.ContainerImage{
    45  						{
    46  							Names: []string{
    47  								"gcr.io/10:v1",
    48  							},
    49  							SizeBytes: int64(10 * mb),
    50  						},
    51  						{
    52  							Names: []string{
    53  								"gcr.io/200:v1",
    54  							},
    55  							SizeBytes: int64(200 * mb),
    56  						},
    57  					},
    58  				},
    59  			},
    60  			imageExistenceMap: map[string]sets.Set[string]{
    61  				"gcr.io/10:v1":  sets.New("node-0", "node-1"),
    62  				"gcr.io/200:v1": sets.New("node-0"),
    63  			},
    64  			expected: map[string]*framework.ImageStateSummary{
    65  				"gcr.io/10:v1": {
    66  					Size:     int64(10 * mb),
    67  					NumNodes: 2,
    68  				},
    69  				"gcr.io/200:v1": {
    70  					Size:     int64(200 * mb),
    71  					NumNodes: 1,
    72  				},
    73  			},
    74  		},
    75  		{
    76  			node: &v1.Node{
    77  				ObjectMeta: metav1.ObjectMeta{Name: "node-0"},
    78  				Status:     v1.NodeStatus{},
    79  			},
    80  			imageExistenceMap: map[string]sets.Set[string]{
    81  				"gcr.io/10:v1":  sets.New("node-1"),
    82  				"gcr.io/200:v1": sets.New[string](),
    83  			},
    84  			expected: map[string]*framework.ImageStateSummary{},
    85  		},
    86  	}
    87  
    88  	for i, test := range tests {
    89  		t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) {
    90  			imageStates := getNodeImageStates(test.node, test.imageExistenceMap)
    91  			if diff := cmp.Diff(test.expected, imageStates); diff != "" {
    92  				t.Errorf("Unexpected imageStates (-want, +got):\n%s", diff)
    93  			}
    94  		})
    95  	}
    96  }
    97  
    98  func TestCreateImageExistenceMap(t *testing.T) {
    99  	tests := []struct {
   100  		nodes    []*v1.Node
   101  		expected map[string]sets.Set[string]
   102  	}{
   103  		{
   104  			nodes: []*v1.Node{
   105  				{
   106  					ObjectMeta: metav1.ObjectMeta{Name: "node-0"},
   107  					Status: v1.NodeStatus{
   108  						Images: []v1.ContainerImage{
   109  							{
   110  								Names: []string{
   111  									"gcr.io/10:v1",
   112  								},
   113  								SizeBytes: int64(10 * mb),
   114  							},
   115  						},
   116  					},
   117  				},
   118  				{
   119  					ObjectMeta: metav1.ObjectMeta{Name: "node-1"},
   120  					Status: v1.NodeStatus{
   121  						Images: []v1.ContainerImage{
   122  							{
   123  								Names: []string{
   124  									"gcr.io/10:v1",
   125  								},
   126  								SizeBytes: int64(10 * mb),
   127  							},
   128  							{
   129  								Names: []string{
   130  									"gcr.io/200:v1",
   131  								},
   132  								SizeBytes: int64(200 * mb),
   133  							},
   134  						},
   135  					},
   136  				},
   137  			},
   138  			expected: map[string]sets.Set[string]{
   139  				"gcr.io/10:v1":  sets.New("node-0", "node-1"),
   140  				"gcr.io/200:v1": sets.New("node-1"),
   141  			},
   142  		},
   143  		{
   144  			nodes: []*v1.Node{
   145  				{
   146  					ObjectMeta: metav1.ObjectMeta{Name: "node-0"},
   147  					Status:     v1.NodeStatus{},
   148  				},
   149  				{
   150  					ObjectMeta: metav1.ObjectMeta{Name: "node-1"},
   151  					Status: v1.NodeStatus{
   152  						Images: []v1.ContainerImage{
   153  							{
   154  								Names: []string{
   155  									"gcr.io/10:v1",
   156  								},
   157  								SizeBytes: int64(10 * mb),
   158  							},
   159  							{
   160  								Names: []string{
   161  									"gcr.io/200:v1",
   162  								},
   163  								SizeBytes: int64(200 * mb),
   164  							},
   165  						},
   166  					},
   167  				},
   168  			},
   169  			expected: map[string]sets.Set[string]{
   170  				"gcr.io/10:v1":  sets.New("node-1"),
   171  				"gcr.io/200:v1": sets.New("node-1"),
   172  			},
   173  		},
   174  	}
   175  
   176  	for i, test := range tests {
   177  		t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) {
   178  			imageMap := createImageExistenceMap(test.nodes)
   179  			if diff := cmp.Diff(test.expected, imageMap); diff != "" {
   180  				t.Errorf("Unexpected imageMap (-want, +got):\n%s", diff)
   181  			}
   182  		})
   183  	}
   184  }
   185  
   186  func TestCreateUsedPVCSet(t *testing.T) {
   187  	tests := []struct {
   188  		name     string
   189  		pods     []*v1.Pod
   190  		expected sets.Set[string]
   191  	}{
   192  		{
   193  			name:     "empty pods list",
   194  			pods:     []*v1.Pod{},
   195  			expected: sets.New[string](),
   196  		},
   197  		{
   198  			name: "pods not scheduled",
   199  			pods: []*v1.Pod{
   200  				st.MakePod().Name("foo").Namespace("foo").Obj(),
   201  				st.MakePod().Name("bar").Namespace("bar").Obj(),
   202  			},
   203  			expected: sets.New[string](),
   204  		},
   205  		{
   206  			name: "scheduled pods that do not use any PVC",
   207  			pods: []*v1.Pod{
   208  				st.MakePod().Name("foo").Namespace("foo").Node("node-1").Obj(),
   209  				st.MakePod().Name("bar").Namespace("bar").Node("node-2").Obj(),
   210  			},
   211  			expected: sets.New[string](),
   212  		},
   213  		{
   214  			name: "scheduled pods that use PVC",
   215  			pods: []*v1.Pod{
   216  				st.MakePod().Name("foo").Namespace("foo").Node("node-1").PVC("pvc1").Obj(),
   217  				st.MakePod().Name("bar").Namespace("bar").Node("node-2").PVC("pvc2").Obj(),
   218  			},
   219  			expected: sets.New("foo/pvc1", "bar/pvc2"),
   220  		},
   221  	}
   222  
   223  	for _, test := range tests {
   224  		t.Run(test.name, func(t *testing.T) {
   225  			usedPVCs := createUsedPVCSet(test.pods)
   226  			if diff := cmp.Diff(test.expected, usedPVCs); diff != "" {
   227  				t.Errorf("Unexpected usedPVCs (-want +got):\n%s", diff)
   228  			}
   229  		})
   230  	}
   231  }
   232  
   233  func TestNewSnapshot(t *testing.T) {
   234  	podWithAnnotations := st.MakePod().Name("foo").Namespace("ns").Node("node-1").Annotations(map[string]string{"custom": "annotation"}).Obj()
   235  	podWithPort := st.MakePod().Name("foo").Namespace("foo").Node("node-0").ContainerPort([]v1.ContainerPort{{HostPort: 8080}}).Obj()
   236  	podWithAntiAffitiny := st.MakePod().Name("baz").Namespace("ns").PodAntiAffinity("another", &metav1.LabelSelector{MatchLabels: map[string]string{"another": "label"}}, st.PodAntiAffinityWithRequiredReq).Node("node-0").Obj()
   237  	podsWithAffitiny := []*v1.Pod{
   238  		st.MakePod().Name("bar").Namespace("ns").PodAffinity("baz", &metav1.LabelSelector{MatchLabels: map[string]string{"baz": "qux"}}, st.PodAffinityWithRequiredReq).Node("node-2").Obj(),
   239  		st.MakePod().Name("bar").Namespace("ns").PodAffinity("key", &metav1.LabelSelector{MatchLabels: map[string]string{"key": "value"}}, st.PodAffinityWithRequiredReq).Node("node-0").Obj(),
   240  	}
   241  	podsWithPVCs := []*v1.Pod{
   242  		st.MakePod().Name("foo").Namespace("foo").Node("node-0").PVC("pvc0").Obj(),
   243  		st.MakePod().Name("bar").Namespace("bar").Node("node-1").PVC("pvc1").Obj(),
   244  		st.MakePod().Name("baz").Namespace("baz").Node("node-2").PVC("pvc2").Obj(),
   245  	}
   246  	testCases := []struct {
   247  		name                         string
   248  		pods                         []*v1.Pod
   249  		nodes                        []*v1.Node
   250  		expectedNodesInfos           []*framework.NodeInfo
   251  		expectedNumNodes             int
   252  		expectedPodsWithAffinity     int
   253  		expectedPodsWithAntiAffinity int
   254  		expectedUsedPVCSet           sets.Set[string]
   255  	}{
   256  		{
   257  			name:  "no pods no nodes",
   258  			pods:  nil,
   259  			nodes: nil,
   260  		},
   261  		{
   262  			name: "single pod single node",
   263  			pods: []*v1.Pod{
   264  				podWithPort,
   265  			},
   266  			nodes: []*v1.Node{
   267  				{ObjectMeta: metav1.ObjectMeta{Name: "node-0"}},
   268  			},
   269  			expectedNodesInfos: []*framework.NodeInfo{
   270  				{
   271  					Pods: []*framework.PodInfo{
   272  						{Pod: podWithPort},
   273  					},
   274  				},
   275  			},
   276  			expectedNumNodes: 1,
   277  		},
   278  		{
   279  			name: "multiple nodes, pods with PVCs",
   280  			pods: podsWithPVCs,
   281  			nodes: []*v1.Node{
   282  				{ObjectMeta: metav1.ObjectMeta{Name: "node-0"}},
   283  				{ObjectMeta: metav1.ObjectMeta{Name: "node-1"}},
   284  				{ObjectMeta: metav1.ObjectMeta{Name: "node-2"}},
   285  			},
   286  			expectedNodesInfos: []*framework.NodeInfo{
   287  				{
   288  					Pods: []*framework.PodInfo{
   289  						{Pod: podsWithPVCs[0]},
   290  					},
   291  				},
   292  				{
   293  					Pods: []*framework.PodInfo{
   294  						{Pod: podsWithPVCs[1]},
   295  					},
   296  				},
   297  				{
   298  					Pods: []*framework.PodInfo{
   299  						{Pod: podsWithPVCs[2]},
   300  					},
   301  				},
   302  			},
   303  			expectedNumNodes:   3,
   304  			expectedUsedPVCSet: sets.New("foo/pvc0", "bar/pvc1", "baz/pvc2"),
   305  		},
   306  		{
   307  			name: "multiple nodes, pod with affinity",
   308  			pods: []*v1.Pod{
   309  				podWithAnnotations,
   310  				podsWithAffitiny[0],
   311  			},
   312  			nodes: []*v1.Node{
   313  				{ObjectMeta: metav1.ObjectMeta{Name: "node-0"}},
   314  				{ObjectMeta: metav1.ObjectMeta{Name: "node-1"}},
   315  				{ObjectMeta: metav1.ObjectMeta{Name: "node-2", Labels: map[string]string{"baz": "qux"}}},
   316  			},
   317  			expectedNodesInfos: []*framework.NodeInfo{
   318  				{
   319  					Pods: []*framework.PodInfo{},
   320  				},
   321  				{
   322  					Pods: []*framework.PodInfo{
   323  						{Pod: podWithAnnotations},
   324  					},
   325  				},
   326  				{
   327  					Pods: []*framework.PodInfo{
   328  						{
   329  							Pod: podsWithAffitiny[0],
   330  							RequiredAffinityTerms: []framework.AffinityTerm{
   331  								{
   332  									Namespaces:        sets.New("ns"),
   333  									Selector:          labels.SelectorFromSet(map[string]string{"baz": "qux"}),
   334  									TopologyKey:       "baz",
   335  									NamespaceSelector: labels.Nothing(),
   336  								},
   337  							},
   338  						},
   339  					},
   340  				},
   341  			},
   342  			expectedNumNodes:         3,
   343  			expectedPodsWithAffinity: 1,
   344  		},
   345  		{
   346  			name: "multiple nodes, pod with affinity, pod with anti-affinity",
   347  			pods: []*v1.Pod{
   348  				podsWithAffitiny[1],
   349  				podWithAntiAffitiny,
   350  			},
   351  			nodes: []*v1.Node{
   352  				{ObjectMeta: metav1.ObjectMeta{Name: "node-0", Labels: map[string]string{"key": "value"}}},
   353  				{ObjectMeta: metav1.ObjectMeta{Name: "node-1", Labels: map[string]string{"another": "label"}}},
   354  			},
   355  			expectedNodesInfos: []*framework.NodeInfo{
   356  				{
   357  					Pods: []*framework.PodInfo{
   358  						{
   359  							Pod: podsWithAffitiny[1],
   360  							RequiredAffinityTerms: []framework.AffinityTerm{
   361  								{
   362  									Namespaces:        sets.New("ns"),
   363  									Selector:          labels.SelectorFromSet(map[string]string{"key": "value"}),
   364  									TopologyKey:       "key",
   365  									NamespaceSelector: labels.Nothing(),
   366  								},
   367  							},
   368  						},
   369  						{
   370  							Pod: podWithAntiAffitiny,
   371  							RequiredAntiAffinityTerms: []framework.AffinityTerm{
   372  								{
   373  									Namespaces:        sets.New("ns"),
   374  									Selector:          labels.SelectorFromSet(map[string]string{"another": "label"}),
   375  									TopologyKey:       "another",
   376  									NamespaceSelector: labels.Nothing(),
   377  								},
   378  							},
   379  						},
   380  					},
   381  				},
   382  				{
   383  					Pods: []*framework.PodInfo{},
   384  				},
   385  			},
   386  			expectedNumNodes:             2,
   387  			expectedPodsWithAffinity:     1,
   388  			expectedPodsWithAntiAffinity: 1,
   389  		},
   390  	}
   391  
   392  	for _, test := range testCases {
   393  		t.Run(test.name, func(t *testing.T) {
   394  			snapshot := NewSnapshot(test.pods, test.nodes)
   395  
   396  			if test.expectedNumNodes != snapshot.NumNodes() {
   397  				t.Errorf("unexpected number of nodes, want: %v, got: %v", test.expectedNumNodes, snapshot.NumNodes())
   398  			}
   399  
   400  			for i, node := range test.nodes {
   401  				info, err := snapshot.Get(node.Name)
   402  				if err != nil {
   403  					t.Errorf("unexpected error but got %s", err)
   404  				}
   405  				if info == nil {
   406  					t.Error("node infos should not be nil")
   407  				}
   408  				for j := range test.expectedNodesInfos[i].Pods {
   409  					if diff := cmp.Diff(test.expectedNodesInfos[i].Pods[j], info.Pods[j]); diff != "" {
   410  						t.Errorf("Unexpected PodInfo (-want +got):\n%s", diff)
   411  					}
   412  				}
   413  			}
   414  
   415  			affinityList, err := snapshot.HavePodsWithAffinityList()
   416  			if err != nil {
   417  				t.Errorf("unexpected error but got %s", err)
   418  			}
   419  			if test.expectedPodsWithAffinity != len(affinityList) {
   420  				t.Errorf("unexpected affinityList number, want: %v, got: %v", test.expectedPodsWithAffinity, len(affinityList))
   421  			}
   422  
   423  			antiAffinityList, err := snapshot.HavePodsWithRequiredAntiAffinityList()
   424  			if err != nil {
   425  				t.Errorf("unexpected error but got %s", err)
   426  			}
   427  			if test.expectedPodsWithAntiAffinity != len(antiAffinityList) {
   428  				t.Errorf("unexpected antiAffinityList number, want: %v, got: %v", test.expectedPodsWithAntiAffinity, len(antiAffinityList))
   429  			}
   430  
   431  			for key := range test.expectedUsedPVCSet {
   432  				if !snapshot.IsPVCUsedByPods(key) {
   433  					t.Errorf("unexpected IsPVCUsedByPods for %s, want: true, got: false", key)
   434  				}
   435  			}
   436  
   437  			if diff := cmp.Diff(test.expectedUsedPVCSet, snapshot.usedPVCSet); diff != "" {
   438  				t.Errorf("Unexpected usedPVCSet (-want +got):\n%s", diff)
   439  			}
   440  		})
   441  	}
   442  }