...

Source file src/k8s.io/kubernetes/pkg/kubelet/cm/devicemanager/topology_hints_test.go

Documentation: k8s.io/kubernetes/pkg/kubelet/cm/devicemanager

     1  /*
     2  Copyright 2019 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 devicemanager
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"sort"
    23  	"testing"
    24  
    25  	v1 "k8s.io/api/core/v1"
    26  	"k8s.io/apimachinery/pkg/api/resource"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/util/sets"
    29  	pluginapi "k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1"
    30  	"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager"
    31  	"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
    32  )
    33  
    34  type mockAffinityStore struct {
    35  	hint topologymanager.TopologyHint
    36  }
    37  
    38  func (m *mockAffinityStore) GetAffinity(podUID string, containerName string) topologymanager.TopologyHint {
    39  	return m.hint
    40  }
    41  
    42  func (m *mockAffinityStore) GetPolicy() topologymanager.Policy {
    43  	return nil
    44  }
    45  
    46  func makeNUMADevice(id string, numa int) pluginapi.Device {
    47  	return pluginapi.Device{
    48  		ID:       id,
    49  		Topology: &pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{{ID: int64(numa)}}},
    50  	}
    51  }
    52  
    53  func makeSocketMask(sockets ...int) bitmask.BitMask {
    54  	mask, _ := bitmask.NewBitMask(sockets...)
    55  	return mask
    56  }
    57  
    58  func TestGetTopologyHints(t *testing.T) {
    59  	tcases := getCommonTestCases()
    60  
    61  	for _, tc := range tcases {
    62  		m := ManagerImpl{
    63  			allDevices:       NewResourceDeviceInstances(),
    64  			healthyDevices:   make(map[string]sets.Set[string]),
    65  			allocatedDevices: make(map[string]sets.Set[string]),
    66  			podDevices:       newPodDevices(),
    67  			sourcesReady:     &sourcesReadyStub{},
    68  			activePods:       func() []*v1.Pod { return []*v1.Pod{tc.pod} },
    69  			numaNodes:        []int{0, 1},
    70  		}
    71  
    72  		for r := range tc.devices {
    73  			m.allDevices[r] = make(DeviceInstances)
    74  			m.healthyDevices[r] = sets.New[string]()
    75  
    76  			for _, d := range tc.devices[r] {
    77  				m.allDevices[r][d.ID] = d
    78  				m.healthyDevices[r].Insert(d.ID)
    79  			}
    80  		}
    81  
    82  		for p := range tc.allocatedDevices {
    83  			for c := range tc.allocatedDevices[p] {
    84  				for r, devices := range tc.allocatedDevices[p][c] {
    85  					m.podDevices.insert(p, c, r, constructDevices(devices), nil)
    86  
    87  					m.allocatedDevices[r] = sets.New[string]()
    88  					for _, d := range devices {
    89  						m.allocatedDevices[r].Insert(d)
    90  					}
    91  				}
    92  			}
    93  		}
    94  
    95  		hints := m.GetTopologyHints(tc.pod, &tc.pod.Spec.Containers[0])
    96  
    97  		for r := range tc.expectedHints {
    98  			sort.SliceStable(hints[r], func(i, j int) bool {
    99  				return hints[r][i].LessThan(hints[r][j])
   100  			})
   101  			sort.SliceStable(tc.expectedHints[r], func(i, j int) bool {
   102  				return tc.expectedHints[r][i].LessThan(tc.expectedHints[r][j])
   103  			})
   104  			if !reflect.DeepEqual(hints[r], tc.expectedHints[r]) {
   105  				t.Errorf("%v: Expected result to be %#v, got %#v", tc.description, tc.expectedHints[r], hints[r])
   106  			}
   107  		}
   108  	}
   109  }
   110  
   111  func TestTopologyAlignedAllocation(t *testing.T) {
   112  	tcases := []struct {
   113  		description                 string
   114  		resource                    string
   115  		request                     int
   116  		devices                     []pluginapi.Device
   117  		allocatedDevices            []string
   118  		hint                        topologymanager.TopologyHint
   119  		getPreferredAllocationFunc  func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error)
   120  		expectedPreferredAllocation []string
   121  		expectedAlignment           map[int]int
   122  	}{
   123  		{
   124  			description: "Single Request, no alignment",
   125  			resource:    "resource",
   126  			request:     1,
   127  			devices: []pluginapi.Device{
   128  				{ID: "Dev1"},
   129  				{ID: "Dev2"},
   130  			},
   131  			hint: topologymanager.TopologyHint{
   132  				NUMANodeAffinity: makeSocketMask(0, 1),
   133  				Preferred:        true,
   134  			},
   135  			expectedAlignment: map[int]int{},
   136  		},
   137  		{
   138  			description: "Request for 1, partial alignment",
   139  			resource:    "resource",
   140  			request:     1,
   141  			devices: []pluginapi.Device{
   142  				{ID: "Dev1"},
   143  				makeNUMADevice("Dev2", 1),
   144  			},
   145  			hint: topologymanager.TopologyHint{
   146  				NUMANodeAffinity: makeSocketMask(1),
   147  				Preferred:        true,
   148  			},
   149  			expectedAlignment: map[int]int{1: 1},
   150  		},
   151  		{
   152  			description: "Single Request, socket 0",
   153  			resource:    "resource",
   154  			request:     1,
   155  			devices: []pluginapi.Device{
   156  				makeNUMADevice("Dev1", 0),
   157  				makeNUMADevice("Dev2", 1),
   158  			},
   159  			hint: topologymanager.TopologyHint{
   160  				NUMANodeAffinity: makeSocketMask(0),
   161  				Preferred:        true,
   162  			},
   163  			expectedAlignment: map[int]int{0: 1},
   164  		},
   165  		{
   166  			description: "Single Request, socket 1",
   167  			resource:    "resource",
   168  			request:     1,
   169  			devices: []pluginapi.Device{
   170  				makeNUMADevice("Dev1", 0),
   171  				makeNUMADevice("Dev2", 1),
   172  			},
   173  			hint: topologymanager.TopologyHint{
   174  				NUMANodeAffinity: makeSocketMask(1),
   175  				Preferred:        true,
   176  			},
   177  			expectedAlignment: map[int]int{1: 1},
   178  		},
   179  		{
   180  			description: "Request for 2, socket 0",
   181  			resource:    "resource",
   182  			request:     2,
   183  			devices: []pluginapi.Device{
   184  				makeNUMADevice("Dev1", 0),
   185  				makeNUMADevice("Dev2", 1),
   186  				makeNUMADevice("Dev3", 0),
   187  				makeNUMADevice("Dev4", 1),
   188  			},
   189  			hint: topologymanager.TopologyHint{
   190  				NUMANodeAffinity: makeSocketMask(0),
   191  				Preferred:        true,
   192  			},
   193  			expectedAlignment: map[int]int{0: 2},
   194  		},
   195  		{
   196  			description: "Request for 2, socket 1",
   197  			resource:    "resource",
   198  			request:     2,
   199  			devices: []pluginapi.Device{
   200  				makeNUMADevice("Dev1", 0),
   201  				makeNUMADevice("Dev2", 1),
   202  				makeNUMADevice("Dev3", 0),
   203  				makeNUMADevice("Dev4", 1),
   204  			},
   205  			hint: topologymanager.TopologyHint{
   206  				NUMANodeAffinity: makeSocketMask(1),
   207  				Preferred:        true,
   208  			},
   209  			expectedAlignment: map[int]int{1: 2},
   210  		},
   211  		{
   212  			description: "Request for 4, unsatisfiable, prefer socket 0",
   213  			resource:    "resource",
   214  			request:     4,
   215  			devices: []pluginapi.Device{
   216  				makeNUMADevice("Dev1", 0),
   217  				makeNUMADevice("Dev2", 1),
   218  				makeNUMADevice("Dev3", 0),
   219  				makeNUMADevice("Dev4", 1),
   220  				makeNUMADevice("Dev5", 0),
   221  				makeNUMADevice("Dev6", 1),
   222  			},
   223  			hint: topologymanager.TopologyHint{
   224  				NUMANodeAffinity: makeSocketMask(0),
   225  				Preferred:        true,
   226  			},
   227  			expectedAlignment: map[int]int{0: 3, 1: 1},
   228  		},
   229  		{
   230  			description: "Request for 4, unsatisfiable, prefer socket 1",
   231  			resource:    "resource",
   232  			request:     4,
   233  			devices: []pluginapi.Device{
   234  				makeNUMADevice("Dev1", 0),
   235  				makeNUMADevice("Dev2", 1),
   236  				makeNUMADevice("Dev3", 0),
   237  				makeNUMADevice("Dev4", 1),
   238  				makeNUMADevice("Dev5", 0),
   239  				makeNUMADevice("Dev6", 1),
   240  			},
   241  			hint: topologymanager.TopologyHint{
   242  				NUMANodeAffinity: makeSocketMask(1),
   243  				Preferred:        true,
   244  			},
   245  			expectedAlignment: map[int]int{0: 1, 1: 3},
   246  		},
   247  		{
   248  			description: "Request for 4, multisocket",
   249  			resource:    "resource",
   250  			request:     4,
   251  			devices: []pluginapi.Device{
   252  				makeNUMADevice("Dev1", 0),
   253  				makeNUMADevice("Dev2", 1),
   254  				makeNUMADevice("Dev3", 2),
   255  				makeNUMADevice("Dev4", 3),
   256  				makeNUMADevice("Dev5", 0),
   257  				makeNUMADevice("Dev6", 1),
   258  				makeNUMADevice("Dev7", 2),
   259  				makeNUMADevice("Dev8", 3),
   260  			},
   261  			hint: topologymanager.TopologyHint{
   262  				NUMANodeAffinity: makeSocketMask(1, 3),
   263  				Preferred:        true,
   264  			},
   265  			expectedAlignment: map[int]int{1: 2, 3: 2},
   266  		},
   267  		{
   268  			description: "Request for 5, socket 0, preferred aligned accepted",
   269  			resource:    "resource",
   270  			request:     5,
   271  			devices: func() []pluginapi.Device {
   272  				devices := []pluginapi.Device{}
   273  				for i := 0; i < 100; i++ {
   274  					id := fmt.Sprintf("Dev%d", i)
   275  					devices = append(devices, makeNUMADevice(id, 0))
   276  				}
   277  				for i := 100; i < 200; i++ {
   278  					id := fmt.Sprintf("Dev%d", i)
   279  					devices = append(devices, makeNUMADevice(id, 1))
   280  				}
   281  				return devices
   282  			}(),
   283  			hint: topologymanager.TopologyHint{
   284  				NUMANodeAffinity: makeSocketMask(0),
   285  				Preferred:        true,
   286  			},
   287  			getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) {
   288  				return &pluginapi.PreferredAllocationResponse{
   289  					ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{
   290  						{DeviceIDs: []string{"Dev0", "Dev19", "Dev83", "Dev42", "Dev77"}},
   291  					},
   292  				}, nil
   293  			},
   294  			expectedPreferredAllocation: []string{"Dev0", "Dev19", "Dev83", "Dev42", "Dev77"},
   295  			expectedAlignment:           map[int]int{0: 5},
   296  		},
   297  		{
   298  			description: "Request for 5, socket 0, preferred aligned accepted, unaligned ignored",
   299  			resource:    "resource",
   300  			request:     5,
   301  			devices: func() []pluginapi.Device {
   302  				devices := []pluginapi.Device{}
   303  				for i := 0; i < 100; i++ {
   304  					id := fmt.Sprintf("Dev%d", i)
   305  					devices = append(devices, makeNUMADevice(id, 0))
   306  				}
   307  				for i := 100; i < 200; i++ {
   308  					id := fmt.Sprintf("Dev%d", i)
   309  					devices = append(devices, makeNUMADevice(id, 1))
   310  				}
   311  				return devices
   312  			}(),
   313  			hint: topologymanager.TopologyHint{
   314  				NUMANodeAffinity: makeSocketMask(0),
   315  				Preferred:        true,
   316  			},
   317  			getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) {
   318  				return &pluginapi.PreferredAllocationResponse{
   319  					ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{
   320  						{DeviceIDs: []string{"Dev0", "Dev19", "Dev83", "Dev150", "Dev186"}},
   321  					},
   322  				}, nil
   323  			},
   324  			expectedPreferredAllocation: []string{"Dev0", "Dev19", "Dev83"},
   325  			expectedAlignment:           map[int]int{0: 5},
   326  		},
   327  		{
   328  			description: "Request for 5, socket 1, preferred aligned accepted, bogus ignored",
   329  			resource:    "resource",
   330  			request:     5,
   331  			devices: func() []pluginapi.Device {
   332  				devices := []pluginapi.Device{}
   333  				for i := 0; i < 100; i++ {
   334  					id := fmt.Sprintf("Dev%d", i)
   335  					devices = append(devices, makeNUMADevice(id, 1))
   336  				}
   337  				return devices
   338  			}(),
   339  			hint: topologymanager.TopologyHint{
   340  				NUMANodeAffinity: makeSocketMask(1),
   341  				Preferred:        true,
   342  			},
   343  			getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) {
   344  				return &pluginapi.PreferredAllocationResponse{
   345  					ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{
   346  						{DeviceIDs: []string{"Dev0", "Dev19", "Dev83", "bogus0", "bogus1"}},
   347  					},
   348  				}, nil
   349  			},
   350  			expectedPreferredAllocation: []string{"Dev0", "Dev19", "Dev83"},
   351  			expectedAlignment:           map[int]int{1: 5},
   352  		},
   353  		{
   354  			description: "Request for 5, multisocket, preferred accepted",
   355  			resource:    "resource",
   356  			request:     5,
   357  			devices: func() []pluginapi.Device {
   358  				devices := []pluginapi.Device{}
   359  				for i := 0; i < 3; i++ {
   360  					id := fmt.Sprintf("Dev%d", i)
   361  					devices = append(devices, makeNUMADevice(id, 0))
   362  				}
   363  				for i := 3; i < 100; i++ {
   364  					id := fmt.Sprintf("Dev%d", i)
   365  					devices = append(devices, makeNUMADevice(id, 1))
   366  				}
   367  				return devices
   368  			}(),
   369  			hint: topologymanager.TopologyHint{
   370  				NUMANodeAffinity: makeSocketMask(0),
   371  				Preferred:        true,
   372  			},
   373  			getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) {
   374  				return &pluginapi.PreferredAllocationResponse{
   375  					ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{
   376  						{DeviceIDs: []string{"Dev0", "Dev1", "Dev2", "Dev42", "Dev83"}},
   377  					},
   378  				}, nil
   379  			},
   380  			expectedPreferredAllocation: []string{"Dev0", "Dev1", "Dev2", "Dev42", "Dev83"},
   381  			expectedAlignment:           map[int]int{0: 3, 1: 2},
   382  		},
   383  		{
   384  			description: "Request for 5, multisocket, preferred unaligned accepted, bogus ignored",
   385  			resource:    "resource",
   386  			request:     5,
   387  			devices: func() []pluginapi.Device {
   388  				devices := []pluginapi.Device{}
   389  				for i := 0; i < 3; i++ {
   390  					id := fmt.Sprintf("Dev%d", i)
   391  					devices = append(devices, makeNUMADevice(id, 0))
   392  				}
   393  				for i := 3; i < 100; i++ {
   394  					id := fmt.Sprintf("Dev%d", i)
   395  					devices = append(devices, makeNUMADevice(id, 1))
   396  				}
   397  				return devices
   398  			}(),
   399  			hint: topologymanager.TopologyHint{
   400  				NUMANodeAffinity: makeSocketMask(0),
   401  				Preferred:        true,
   402  			},
   403  			getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) {
   404  				return &pluginapi.PreferredAllocationResponse{
   405  					ContainerResponses: []*pluginapi.ContainerPreferredAllocationResponse{
   406  						{DeviceIDs: []string{"Dev0", "Dev1", "Dev2", "Dev42", "bogus0"}},
   407  					},
   408  				}, nil
   409  			},
   410  			expectedPreferredAllocation: []string{"Dev0", "Dev1", "Dev2", "Dev42"},
   411  			expectedAlignment:           map[int]int{0: 3, 1: 2},
   412  		},
   413  	}
   414  	for _, tc := range tcases {
   415  		m := ManagerImpl{
   416  			allDevices:            NewResourceDeviceInstances(),
   417  			healthyDevices:        make(map[string]sets.Set[string]),
   418  			allocatedDevices:      make(map[string]sets.Set[string]),
   419  			endpoints:             make(map[string]endpointInfo),
   420  			podDevices:            newPodDevices(),
   421  			sourcesReady:          &sourcesReadyStub{},
   422  			activePods:            func() []*v1.Pod { return []*v1.Pod{} },
   423  			topologyAffinityStore: &mockAffinityStore{tc.hint},
   424  		}
   425  
   426  		m.allDevices[tc.resource] = make(DeviceInstances)
   427  		m.healthyDevices[tc.resource] = sets.New[string]()
   428  		m.endpoints[tc.resource] = endpointInfo{}
   429  
   430  		for _, d := range tc.devices {
   431  			m.allDevices[tc.resource][d.ID] = d
   432  			m.healthyDevices[tc.resource].Insert(d.ID)
   433  		}
   434  
   435  		if tc.getPreferredAllocationFunc != nil {
   436  			m.endpoints[tc.resource] = endpointInfo{
   437  				e: &MockEndpoint{
   438  					getPreferredAllocationFunc: tc.getPreferredAllocationFunc,
   439  				},
   440  				opts: &pluginapi.DevicePluginOptions{GetPreferredAllocationAvailable: true},
   441  			}
   442  		}
   443  
   444  		allocated, err := m.devicesToAllocate("podUID", "containerName", tc.resource, tc.request, sets.New[string]())
   445  		if err != nil {
   446  			t.Errorf("Unexpected error: %v", err)
   447  			continue
   448  		}
   449  
   450  		if len(allocated) != tc.request {
   451  			t.Errorf("%v. expected allocation size: %v but got: %v", tc.description, tc.request, len(allocated))
   452  		}
   453  
   454  		if !allocated.HasAll(tc.expectedPreferredAllocation...) {
   455  			t.Errorf("%v. expected preferred allocation: %v but not present in: %v", tc.description, tc.expectedPreferredAllocation, allocated.UnsortedList())
   456  		}
   457  
   458  		alignment := make(map[int]int)
   459  		if m.deviceHasTopologyAlignment(tc.resource) {
   460  			for d := range allocated {
   461  				if m.allDevices[tc.resource][d].Topology != nil {
   462  					alignment[int(m.allDevices[tc.resource][d].Topology.Nodes[0].ID)]++
   463  				}
   464  			}
   465  		}
   466  
   467  		if !reflect.DeepEqual(alignment, tc.expectedAlignment) {
   468  			t.Errorf("%v. expected alignment: %v but got: %v", tc.description, tc.expectedAlignment, alignment)
   469  		}
   470  	}
   471  }
   472  
   473  func TestGetPreferredAllocationParameters(t *testing.T) {
   474  	tcases := []struct {
   475  		description         string
   476  		resource            string
   477  		request             int
   478  		allDevices          []pluginapi.Device
   479  		allocatedDevices    []string
   480  		reusableDevices     []string
   481  		hint                topologymanager.TopologyHint
   482  		expectedAvailable   []string
   483  		expectedMustInclude []string
   484  		expectedSize        int
   485  	}{
   486  		{
   487  			description: "Request for 1, socket 0, 0 already allocated, 0 reusable",
   488  			resource:    "resource",
   489  			request:     1,
   490  			allDevices: []pluginapi.Device{
   491  				makeNUMADevice("Dev0", 0),
   492  				makeNUMADevice("Dev1", 0),
   493  				makeNUMADevice("Dev2", 0),
   494  				makeNUMADevice("Dev3", 0),
   495  			},
   496  			allocatedDevices: []string{},
   497  			reusableDevices:  []string{},
   498  			hint: topologymanager.TopologyHint{
   499  				NUMANodeAffinity: makeSocketMask(0),
   500  				Preferred:        true,
   501  			},
   502  			expectedAvailable:   []string{"Dev0", "Dev1", "Dev2", "Dev3"},
   503  			expectedMustInclude: []string{},
   504  			expectedSize:        1,
   505  		},
   506  		{
   507  			description: "Request for 4, socket 0, 2 already allocated, 2 reusable",
   508  			resource:    "resource",
   509  			request:     4,
   510  			allDevices: []pluginapi.Device{
   511  				makeNUMADevice("Dev0", 0),
   512  				makeNUMADevice("Dev1", 0),
   513  				makeNUMADevice("Dev2", 0),
   514  				makeNUMADevice("Dev3", 0),
   515  				makeNUMADevice("Dev4", 0),
   516  				makeNUMADevice("Dev5", 0),
   517  				makeNUMADevice("Dev6", 0),
   518  				makeNUMADevice("Dev7", 0),
   519  			},
   520  			allocatedDevices: []string{"Dev0", "Dev5"},
   521  			reusableDevices:  []string{"Dev0", "Dev5"},
   522  			hint: topologymanager.TopologyHint{
   523  				NUMANodeAffinity: makeSocketMask(0),
   524  				Preferred:        true,
   525  			},
   526  			expectedAvailable:   []string{"Dev0", "Dev1", "Dev2", "Dev3", "Dev4", "Dev5", "Dev6", "Dev7"},
   527  			expectedMustInclude: []string{"Dev0", "Dev5"},
   528  			expectedSize:        4,
   529  		},
   530  		{
   531  			description: "Request for 4, socket 0, 4 already allocated, 2 reusable",
   532  			resource:    "resource",
   533  			request:     4,
   534  			allDevices: []pluginapi.Device{
   535  				makeNUMADevice("Dev0", 0),
   536  				makeNUMADevice("Dev1", 0),
   537  				makeNUMADevice("Dev2", 0),
   538  				makeNUMADevice("Dev3", 0),
   539  				makeNUMADevice("Dev4", 0),
   540  				makeNUMADevice("Dev5", 0),
   541  				makeNUMADevice("Dev6", 0),
   542  				makeNUMADevice("Dev7", 0),
   543  			},
   544  			allocatedDevices: []string{"Dev0", "Dev5", "Dev4", "Dev1"},
   545  			reusableDevices:  []string{"Dev0", "Dev5"},
   546  			hint: topologymanager.TopologyHint{
   547  				NUMANodeAffinity: makeSocketMask(0),
   548  				Preferred:        true,
   549  			},
   550  			expectedAvailable:   []string{"Dev0", "Dev2", "Dev3", "Dev5", "Dev6", "Dev7"},
   551  			expectedMustInclude: []string{"Dev0", "Dev5"},
   552  			expectedSize:        4,
   553  		},
   554  		{
   555  			description: "Request for 6, multisocket, 2 already allocated, 2 reusable",
   556  			resource:    "resource",
   557  			request:     6,
   558  			allDevices: []pluginapi.Device{
   559  				makeNUMADevice("Dev0", 0),
   560  				makeNUMADevice("Dev1", 0),
   561  				makeNUMADevice("Dev2", 0),
   562  				makeNUMADevice("Dev3", 0),
   563  				makeNUMADevice("Dev4", 1),
   564  				makeNUMADevice("Dev5", 1),
   565  				makeNUMADevice("Dev6", 1),
   566  				makeNUMADevice("Dev7", 1),
   567  			},
   568  			allocatedDevices: []string{"Dev1", "Dev6"},
   569  			reusableDevices:  []string{"Dev1", "Dev6"},
   570  			hint: topologymanager.TopologyHint{
   571  				NUMANodeAffinity: makeSocketMask(0),
   572  				Preferred:        true,
   573  			},
   574  			expectedAvailable:   []string{"Dev0", "Dev1", "Dev2", "Dev3", "Dev4", "Dev5", "Dev6", "Dev7"},
   575  			expectedMustInclude: []string{"Dev0", "Dev1", "Dev2", "Dev3", "Dev6"},
   576  			expectedSize:        6,
   577  		},
   578  		{
   579  			description: "Request for 6, multisocket, 4 already allocated, 2 reusable",
   580  			resource:    "resource",
   581  			request:     6,
   582  			allDevices: []pluginapi.Device{
   583  				makeNUMADevice("Dev0", 0),
   584  				makeNUMADevice("Dev1", 0),
   585  				makeNUMADevice("Dev2", 0),
   586  				makeNUMADevice("Dev3", 0),
   587  				makeNUMADevice("Dev4", 1),
   588  				makeNUMADevice("Dev5", 1),
   589  				makeNUMADevice("Dev6", 1),
   590  				makeNUMADevice("Dev7", 1),
   591  			},
   592  			allocatedDevices: []string{"Dev0", "Dev1", "Dev6", "Dev7"},
   593  			reusableDevices:  []string{"Dev1", "Dev6"},
   594  			hint: topologymanager.TopologyHint{
   595  				NUMANodeAffinity: makeSocketMask(0),
   596  				Preferred:        true,
   597  			},
   598  			expectedAvailable:   []string{"Dev1", "Dev2", "Dev3", "Dev4", "Dev5", "Dev6"},
   599  			expectedMustInclude: []string{"Dev1", "Dev2", "Dev3", "Dev6"},
   600  			expectedSize:        6,
   601  		},
   602  	}
   603  	for _, tc := range tcases {
   604  		m := ManagerImpl{
   605  			allDevices:            NewResourceDeviceInstances(),
   606  			healthyDevices:        make(map[string]sets.Set[string]),
   607  			allocatedDevices:      make(map[string]sets.Set[string]),
   608  			endpoints:             make(map[string]endpointInfo),
   609  			podDevices:            newPodDevices(),
   610  			sourcesReady:          &sourcesReadyStub{},
   611  			activePods:            func() []*v1.Pod { return []*v1.Pod{} },
   612  			topologyAffinityStore: &mockAffinityStore{tc.hint},
   613  		}
   614  
   615  		m.allDevices[tc.resource] = make(DeviceInstances)
   616  		m.healthyDevices[tc.resource] = sets.New[string]()
   617  		for _, d := range tc.allDevices {
   618  			m.allDevices[tc.resource][d.ID] = d
   619  			m.healthyDevices[tc.resource].Insert(d.ID)
   620  		}
   621  
   622  		m.allocatedDevices[tc.resource] = sets.New[string]()
   623  		for _, d := range tc.allocatedDevices {
   624  			m.allocatedDevices[tc.resource].Insert(d)
   625  		}
   626  
   627  		actualAvailable := []string{}
   628  		actualMustInclude := []string{}
   629  		actualSize := 0
   630  		m.endpoints[tc.resource] = endpointInfo{
   631  			e: &MockEndpoint{
   632  				getPreferredAllocationFunc: func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) {
   633  					actualAvailable = append(actualAvailable, available...)
   634  					actualMustInclude = append(actualMustInclude, mustInclude...)
   635  					actualSize = size
   636  					return nil, nil
   637  				},
   638  			},
   639  			opts: &pluginapi.DevicePluginOptions{GetPreferredAllocationAvailable: true},
   640  		}
   641  
   642  		_, err := m.devicesToAllocate("podUID", "containerName", tc.resource, tc.request, sets.New[string](tc.reusableDevices...))
   643  		if err != nil {
   644  			t.Errorf("Unexpected error: %v", err)
   645  			continue
   646  		}
   647  
   648  		if !sets.New[string](actualAvailable...).Equal(sets.New[string](tc.expectedAvailable...)) {
   649  			t.Errorf("%v. expected available: %v but got: %v", tc.description, tc.expectedAvailable, actualAvailable)
   650  		}
   651  
   652  		if !sets.New[string](actualAvailable...).Equal(sets.New[string](tc.expectedAvailable...)) {
   653  			t.Errorf("%v. expected mustInclude: %v but got: %v", tc.description, tc.expectedMustInclude, actualMustInclude)
   654  		}
   655  
   656  		if actualSize != tc.expectedSize {
   657  			t.Errorf("%v. expected size: %v but got: %v", tc.description, tc.expectedSize, actualSize)
   658  		}
   659  	}
   660  }
   661  
   662  func TestGetPodDeviceRequest(t *testing.T) {
   663  	tcases := []struct {
   664  		description       string
   665  		pod               *v1.Pod
   666  		registeredDevices []string
   667  		expected          map[string]int
   668  	}{
   669  		{
   670  			description:       "empty pod",
   671  			pod:               &v1.Pod{},
   672  			registeredDevices: []string{},
   673  			expected:          map[string]int{},
   674  		},
   675  		{
   676  			description: "Init container requests device plugin resource",
   677  			pod: &v1.Pod{
   678  				Spec: v1.PodSpec{
   679  					InitContainers: []v1.Container{
   680  						{
   681  							Resources: v1.ResourceRequirements{
   682  								Limits: v1.ResourceList{
   683  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   684  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   685  									v1.ResourceName("gpu"):             resource.MustParse("2"),
   686  								},
   687  							},
   688  						},
   689  					},
   690  				},
   691  			},
   692  			registeredDevices: []string{"gpu"},
   693  			expected:          map[string]int{"gpu": 2},
   694  		},
   695  		{
   696  			description: "Init containers request device plugin resource",
   697  			pod: &v1.Pod{
   698  				Spec: v1.PodSpec{
   699  					InitContainers: []v1.Container{
   700  						{
   701  							Resources: v1.ResourceRequirements{
   702  								Limits: v1.ResourceList{
   703  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   704  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   705  									v1.ResourceName("gpu"):             resource.MustParse("2"),
   706  								},
   707  							},
   708  						},
   709  						{
   710  							Resources: v1.ResourceRequirements{
   711  								Limits: v1.ResourceList{
   712  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   713  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   714  									v1.ResourceName("gpu"):             resource.MustParse("4"),
   715  								},
   716  							},
   717  						},
   718  					},
   719  				},
   720  			},
   721  			registeredDevices: []string{"gpu"},
   722  			expected:          map[string]int{"gpu": 4},
   723  		},
   724  		{
   725  			description: "User container requests device plugin resource",
   726  			pod: &v1.Pod{
   727  				Spec: v1.PodSpec{
   728  					Containers: []v1.Container{
   729  						{
   730  							Resources: v1.ResourceRequirements{
   731  								Limits: v1.ResourceList{
   732  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   733  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   734  									v1.ResourceName("gpu"):             resource.MustParse("2"),
   735  								},
   736  							},
   737  						},
   738  					},
   739  				},
   740  			},
   741  			registeredDevices: []string{"gpu"},
   742  			expected:          map[string]int{"gpu": 2},
   743  		},
   744  		{
   745  			description: "Init containers and user containers request the same amount of device plugin resources",
   746  			pod: &v1.Pod{
   747  				Spec: v1.PodSpec{
   748  					InitContainers: []v1.Container{
   749  						{
   750  							Resources: v1.ResourceRequirements{
   751  								Limits: v1.ResourceList{
   752  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   753  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   754  									v1.ResourceName("gpu"):             resource.MustParse("2"),
   755  									v1.ResourceName("nic"):             resource.MustParse("2"),
   756  								},
   757  							},
   758  						},
   759  						{
   760  							Resources: v1.ResourceRequirements{
   761  								Limits: v1.ResourceList{
   762  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   763  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   764  									v1.ResourceName("gpu"):             resource.MustParse("2"),
   765  									v1.ResourceName("nic"):             resource.MustParse("2"),
   766  								},
   767  							},
   768  						},
   769  					},
   770  					Containers: []v1.Container{
   771  						{
   772  							Resources: v1.ResourceRequirements{
   773  								Limits: v1.ResourceList{
   774  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   775  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   776  									v1.ResourceName("gpu"):             resource.MustParse("1"),
   777  									v1.ResourceName("nic"):             resource.MustParse("1"),
   778  								},
   779  							},
   780  						},
   781  						{
   782  							Resources: v1.ResourceRequirements{
   783  								Limits: v1.ResourceList{
   784  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   785  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   786  									v1.ResourceName("gpu"):             resource.MustParse("1"),
   787  									v1.ResourceName("nic"):             resource.MustParse("1"),
   788  								},
   789  							},
   790  						},
   791  					},
   792  				},
   793  			},
   794  			registeredDevices: []string{"gpu", "nic"},
   795  			expected:          map[string]int{"gpu": 2, "nic": 2},
   796  		},
   797  		{
   798  			description: "Init containers request more device plugin resources than user containers",
   799  			pod: &v1.Pod{
   800  				Spec: v1.PodSpec{
   801  					InitContainers: []v1.Container{
   802  						{
   803  							Resources: v1.ResourceRequirements{
   804  								Limits: v1.ResourceList{
   805  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   806  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   807  									v1.ResourceName("gpu"):             resource.MustParse("2"),
   808  									v1.ResourceName("nic"):             resource.MustParse("1"),
   809  								},
   810  							},
   811  						},
   812  						{
   813  							Resources: v1.ResourceRequirements{
   814  								Limits: v1.ResourceList{
   815  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   816  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   817  									v1.ResourceName("gpu"):             resource.MustParse("3"),
   818  									v1.ResourceName("nic"):             resource.MustParse("2"),
   819  								},
   820  							},
   821  						},
   822  					},
   823  					Containers: []v1.Container{
   824  						{
   825  							Resources: v1.ResourceRequirements{
   826  								Limits: v1.ResourceList{
   827  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("1"),
   828  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   829  									v1.ResourceName("gpu"):             resource.MustParse("1"),
   830  									v1.ResourceName("nic"):             resource.MustParse("1"),
   831  								},
   832  							},
   833  						},
   834  						{
   835  							Resources: v1.ResourceRequirements{
   836  								Limits: v1.ResourceList{
   837  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("1"),
   838  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   839  									v1.ResourceName("gpu"):             resource.MustParse("1"),
   840  								},
   841  							},
   842  						},
   843  					},
   844  				},
   845  			},
   846  			registeredDevices: []string{"gpu", "nic"},
   847  			expected:          map[string]int{"gpu": 3, "nic": 2},
   848  		},
   849  		{
   850  			description: "User containers request more device plugin resources than init containers",
   851  			pod: &v1.Pod{
   852  				Spec: v1.PodSpec{
   853  					InitContainers: []v1.Container{
   854  						{
   855  							Resources: v1.ResourceRequirements{
   856  								Limits: v1.ResourceList{
   857  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   858  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   859  									v1.ResourceName("gpu"):             resource.MustParse("2"),
   860  									v1.ResourceName("nic"):             resource.MustParse("1"),
   861  								},
   862  							},
   863  						},
   864  						{
   865  							Resources: v1.ResourceRequirements{
   866  								Limits: v1.ResourceList{
   867  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("2"),
   868  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   869  									v1.ResourceName("gpu"):             resource.MustParse("2"),
   870  									v1.ResourceName("nic"):             resource.MustParse("1"),
   871  								},
   872  							},
   873  						},
   874  					},
   875  					Containers: []v1.Container{
   876  						{
   877  							Resources: v1.ResourceRequirements{
   878  								Limits: v1.ResourceList{
   879  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("1"),
   880  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   881  									v1.ResourceName("gpu"):             resource.MustParse("3"),
   882  									v1.ResourceName("nic"):             resource.MustParse("2"),
   883  								},
   884  							},
   885  						},
   886  						{
   887  							Resources: v1.ResourceRequirements{
   888  								Limits: v1.ResourceList{
   889  									v1.ResourceName(v1.ResourceCPU):    resource.MustParse("1"),
   890  									v1.ResourceName(v1.ResourceMemory): resource.MustParse("1G"),
   891  									v1.ResourceName("gpu"):             resource.MustParse("3"),
   892  									v1.ResourceName("nic"):             resource.MustParse("2"),
   893  								},
   894  							},
   895  						},
   896  					},
   897  				},
   898  			},
   899  			registeredDevices: []string{"gpu", "nic"},
   900  			expected:          map[string]int{"gpu": 6, "nic": 4},
   901  		},
   902  	}
   903  
   904  	for _, tc := range tcases {
   905  		m := ManagerImpl{
   906  			healthyDevices: make(map[string]sets.Set[string]),
   907  		}
   908  
   909  		for _, res := range tc.registeredDevices {
   910  			m.healthyDevices[res] = sets.New[string]()
   911  		}
   912  
   913  		accumulatedResourceRequests := m.getPodDeviceRequest(tc.pod)
   914  
   915  		if !reflect.DeepEqual(accumulatedResourceRequests, tc.expected) {
   916  			t.Errorf("%v. expected alignment: %v but got: %v", tc.description, tc.expected, accumulatedResourceRequests)
   917  		}
   918  	}
   919  }
   920  
   921  func TestGetPodTopologyHints(t *testing.T) {
   922  	tcases := getCommonTestCases()
   923  	tcases = append(tcases, getPodScopeTestCases()...)
   924  
   925  	for _, tc := range tcases {
   926  		m := ManagerImpl{
   927  			allDevices:       NewResourceDeviceInstances(),
   928  			healthyDevices:   make(map[string]sets.Set[string]),
   929  			allocatedDevices: make(map[string]sets.Set[string]),
   930  			podDevices:       newPodDevices(),
   931  			sourcesReady:     &sourcesReadyStub{},
   932  			activePods:       func() []*v1.Pod { return []*v1.Pod{tc.pod, {ObjectMeta: metav1.ObjectMeta{UID: "fakeOtherPod"}}} },
   933  			numaNodes:        []int{0, 1},
   934  		}
   935  
   936  		for r := range tc.devices {
   937  			m.allDevices[r] = make(DeviceInstances)
   938  			m.healthyDevices[r] = sets.New[string]()
   939  
   940  			for _, d := range tc.devices[r] {
   941  				//add `pluginapi.Device` with Topology
   942  				m.allDevices[r][d.ID] = d
   943  				m.healthyDevices[r].Insert(d.ID)
   944  			}
   945  		}
   946  
   947  		for p := range tc.allocatedDevices {
   948  			for c := range tc.allocatedDevices[p] {
   949  				for r, devices := range tc.allocatedDevices[p][c] {
   950  					m.podDevices.insert(p, c, r, constructDevices(devices), nil)
   951  
   952  					m.allocatedDevices[r] = sets.New[string]()
   953  					for _, d := range devices {
   954  						m.allocatedDevices[r].Insert(d)
   955  					}
   956  				}
   957  			}
   958  		}
   959  
   960  		hints := m.GetPodTopologyHints(tc.pod)
   961  
   962  		for r := range tc.expectedHints {
   963  			sort.SliceStable(hints[r], func(i, j int) bool {
   964  				return hints[r][i].LessThan(hints[r][j])
   965  			})
   966  			sort.SliceStable(tc.expectedHints[r], func(i, j int) bool {
   967  				return tc.expectedHints[r][i].LessThan(tc.expectedHints[r][j])
   968  			})
   969  			if !reflect.DeepEqual(hints[r], tc.expectedHints[r]) {
   970  				t.Errorf("%v: Expected result to be %v, got %v", tc.description, tc.expectedHints[r], hints[r])
   971  			}
   972  		}
   973  	}
   974  }
   975  
   976  type topologyHintTestCase struct {
   977  	description      string
   978  	pod              *v1.Pod
   979  	devices          map[string][]pluginapi.Device
   980  	allocatedDevices map[string]map[string]map[string][]string
   981  	expectedHints    map[string][]topologymanager.TopologyHint
   982  }
   983  
   984  func getCommonTestCases() []topologyHintTestCase {
   985  	return []topologyHintTestCase{
   986  		{
   987  			description: "Single Request, no alignment",
   988  			pod: &v1.Pod{
   989  				ObjectMeta: metav1.ObjectMeta{
   990  					UID: "fakePod",
   991  				},
   992  				Spec: v1.PodSpec{
   993  					Containers: []v1.Container{
   994  						{
   995  							Name: "fakeContainer",
   996  							Resources: v1.ResourceRequirements{
   997  								Limits: v1.ResourceList{
   998  									v1.ResourceName("testdevice"): resource.MustParse("1"),
   999  								},
  1000  							},
  1001  						},
  1002  					},
  1003  				},
  1004  			},
  1005  			devices: map[string][]pluginapi.Device{
  1006  				"testdevice": {
  1007  					{ID: "Dev1"},
  1008  					{ID: "Dev2"},
  1009  					{ID: "Dev3", Topology: &pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{}}},
  1010  					{ID: "Dev4", Topology: &pluginapi.TopologyInfo{Nodes: nil}},
  1011  				},
  1012  			},
  1013  			expectedHints: map[string][]topologymanager.TopologyHint{
  1014  				"testdevice": nil,
  1015  			},
  1016  		},
  1017  		{
  1018  			description: "Single Request, only one with alignment",
  1019  			pod: &v1.Pod{
  1020  				ObjectMeta: metav1.ObjectMeta{
  1021  					UID: "fakePod",
  1022  				},
  1023  				Spec: v1.PodSpec{
  1024  					Containers: []v1.Container{
  1025  						{
  1026  							Name: "fakeContainer",
  1027  							Resources: v1.ResourceRequirements{
  1028  								Limits: v1.ResourceList{
  1029  									v1.ResourceName("testdevice"): resource.MustParse("1"),
  1030  								},
  1031  							},
  1032  						},
  1033  					},
  1034  				},
  1035  			},
  1036  			devices: map[string][]pluginapi.Device{
  1037  				"testdevice": {
  1038  					{ID: "Dev1"},
  1039  					makeNUMADevice("Dev2", 1),
  1040  				},
  1041  			},
  1042  			expectedHints: map[string][]topologymanager.TopologyHint{
  1043  				"testdevice": {
  1044  					{
  1045  						NUMANodeAffinity: makeSocketMask(1),
  1046  						Preferred:        true,
  1047  					},
  1048  					{
  1049  						NUMANodeAffinity: makeSocketMask(0, 1),
  1050  						Preferred:        false,
  1051  					},
  1052  				},
  1053  			},
  1054  		},
  1055  		{
  1056  			description: "Single Request, one device per socket",
  1057  			pod: &v1.Pod{
  1058  				ObjectMeta: metav1.ObjectMeta{
  1059  					UID: "fakePod",
  1060  				},
  1061  				Spec: v1.PodSpec{
  1062  					Containers: []v1.Container{
  1063  						{
  1064  							Name: "fakeContainer",
  1065  							Resources: v1.ResourceRequirements{
  1066  								Limits: v1.ResourceList{
  1067  									v1.ResourceName("testdevice"): resource.MustParse("1"),
  1068  								},
  1069  							},
  1070  						},
  1071  					},
  1072  				},
  1073  			},
  1074  			devices: map[string][]pluginapi.Device{
  1075  				"testdevice": {
  1076  					makeNUMADevice("Dev1", 0),
  1077  					makeNUMADevice("Dev2", 1),
  1078  				},
  1079  			},
  1080  			expectedHints: map[string][]topologymanager.TopologyHint{
  1081  				"testdevice": {
  1082  					{
  1083  						NUMANodeAffinity: makeSocketMask(0),
  1084  						Preferred:        true,
  1085  					},
  1086  					{
  1087  						NUMANodeAffinity: makeSocketMask(1),
  1088  						Preferred:        true,
  1089  					},
  1090  					{
  1091  						NUMANodeAffinity: makeSocketMask(0, 1),
  1092  						Preferred:        false,
  1093  					},
  1094  				},
  1095  			},
  1096  		},
  1097  		{
  1098  			description: "Request for 2, one device per socket",
  1099  			pod: &v1.Pod{
  1100  				ObjectMeta: metav1.ObjectMeta{
  1101  					UID: "fakePod",
  1102  				},
  1103  				Spec: v1.PodSpec{
  1104  					Containers: []v1.Container{
  1105  						{
  1106  							Name: "fakeContainer",
  1107  							Resources: v1.ResourceRequirements{
  1108  								Limits: v1.ResourceList{
  1109  									v1.ResourceName("testdevice"): resource.MustParse("2"),
  1110  								},
  1111  							},
  1112  						},
  1113  					},
  1114  				},
  1115  			},
  1116  			devices: map[string][]pluginapi.Device{
  1117  				"testdevice": {
  1118  					makeNUMADevice("Dev1", 0),
  1119  					makeNUMADevice("Dev2", 1),
  1120  				},
  1121  			},
  1122  			expectedHints: map[string][]topologymanager.TopologyHint{
  1123  				"testdevice": {
  1124  					{
  1125  						NUMANodeAffinity: makeSocketMask(0, 1),
  1126  						Preferred:        true,
  1127  					},
  1128  				},
  1129  			},
  1130  		},
  1131  		{
  1132  			description: "Request for 2, 2 devices per socket",
  1133  			pod: &v1.Pod{
  1134  				ObjectMeta: metav1.ObjectMeta{
  1135  					UID: "fakePod",
  1136  				},
  1137  				Spec: v1.PodSpec{
  1138  					Containers: []v1.Container{
  1139  						{
  1140  							Name: "fakeContainer",
  1141  							Resources: v1.ResourceRequirements{
  1142  								Limits: v1.ResourceList{
  1143  									v1.ResourceName("testdevice"): resource.MustParse("2"),
  1144  								},
  1145  							},
  1146  						},
  1147  					},
  1148  				},
  1149  			},
  1150  			devices: map[string][]pluginapi.Device{
  1151  				"testdevice": {
  1152  					makeNUMADevice("Dev1", 0),
  1153  					makeNUMADevice("Dev2", 1),
  1154  					makeNUMADevice("Dev3", 0),
  1155  					makeNUMADevice("Dev4", 1),
  1156  				},
  1157  			},
  1158  			expectedHints: map[string][]topologymanager.TopologyHint{
  1159  				"testdevice": {
  1160  					{
  1161  						NUMANodeAffinity: makeSocketMask(0),
  1162  						Preferred:        true,
  1163  					},
  1164  					{
  1165  						NUMANodeAffinity: makeSocketMask(1),
  1166  						Preferred:        true,
  1167  					},
  1168  					{
  1169  						NUMANodeAffinity: makeSocketMask(0, 1),
  1170  						Preferred:        false,
  1171  					},
  1172  				},
  1173  			},
  1174  		},
  1175  		{
  1176  			description: "Request for 2, optimal on 1 NUMA node, forced cross-NUMA",
  1177  			pod: &v1.Pod{
  1178  				ObjectMeta: metav1.ObjectMeta{
  1179  					UID: "fakePod",
  1180  				},
  1181  				Spec: v1.PodSpec{
  1182  					Containers: []v1.Container{
  1183  						{
  1184  							Name: "fakeContainer",
  1185  							Resources: v1.ResourceRequirements{
  1186  								Limits: v1.ResourceList{
  1187  									v1.ResourceName("testdevice"): resource.MustParse("2"),
  1188  								},
  1189  							},
  1190  						},
  1191  					},
  1192  				},
  1193  			},
  1194  			devices: map[string][]pluginapi.Device{
  1195  				"testdevice": {
  1196  					makeNUMADevice("Dev1", 0),
  1197  					makeNUMADevice("Dev2", 1),
  1198  					makeNUMADevice("Dev3", 0),
  1199  					makeNUMADevice("Dev4", 1),
  1200  				},
  1201  			},
  1202  			allocatedDevices: map[string]map[string]map[string][]string{
  1203  				"fakePod": {
  1204  					"fakeOtherContainer": {
  1205  						"testdevice": {"Dev1", "Dev2"},
  1206  					},
  1207  				},
  1208  			},
  1209  			expectedHints: map[string][]topologymanager.TopologyHint{
  1210  				"testdevice": {
  1211  					{
  1212  						NUMANodeAffinity: makeSocketMask(0, 1),
  1213  						Preferred:        false,
  1214  					},
  1215  				},
  1216  			},
  1217  		},
  1218  		{
  1219  			description: "2 device types, mixed configuration",
  1220  			pod: &v1.Pod{
  1221  				ObjectMeta: metav1.ObjectMeta{
  1222  					UID: "fakePod",
  1223  				},
  1224  				Spec: v1.PodSpec{
  1225  					Containers: []v1.Container{
  1226  						{
  1227  							Name: "fakeContainer",
  1228  							Resources: v1.ResourceRequirements{
  1229  								Limits: v1.ResourceList{
  1230  									v1.ResourceName("testdevice1"): resource.MustParse("2"),
  1231  									v1.ResourceName("testdevice2"): resource.MustParse("1"),
  1232  								},
  1233  							},
  1234  						},
  1235  					},
  1236  				},
  1237  			},
  1238  			devices: map[string][]pluginapi.Device{
  1239  				"testdevice1": {
  1240  					makeNUMADevice("Dev1", 0),
  1241  					makeNUMADevice("Dev2", 1),
  1242  					makeNUMADevice("Dev3", 0),
  1243  					makeNUMADevice("Dev4", 1),
  1244  				},
  1245  				"testdevice2": {
  1246  					makeNUMADevice("Dev1", 0),
  1247  				},
  1248  			},
  1249  			expectedHints: map[string][]topologymanager.TopologyHint{
  1250  				"testdevice1": {
  1251  					{
  1252  						NUMANodeAffinity: makeSocketMask(0),
  1253  						Preferred:        true,
  1254  					},
  1255  					{
  1256  						NUMANodeAffinity: makeSocketMask(1),
  1257  						Preferred:        true,
  1258  					},
  1259  					{
  1260  						NUMANodeAffinity: makeSocketMask(0, 1),
  1261  						Preferred:        false,
  1262  					},
  1263  				},
  1264  				"testdevice2": {
  1265  					{
  1266  						NUMANodeAffinity: makeSocketMask(0),
  1267  						Preferred:        true,
  1268  					},
  1269  					{
  1270  						NUMANodeAffinity: makeSocketMask(0, 1),
  1271  						Preferred:        false,
  1272  					},
  1273  				},
  1274  			},
  1275  		},
  1276  		{
  1277  			description: "Single device type, more requested than available",
  1278  			pod: &v1.Pod{
  1279  				ObjectMeta: metav1.ObjectMeta{
  1280  					UID: "fakePod",
  1281  				},
  1282  				Spec: v1.PodSpec{
  1283  					Containers: []v1.Container{
  1284  						{
  1285  							Name: "fakeContainer",
  1286  							Resources: v1.ResourceRequirements{
  1287  								Limits: v1.ResourceList{
  1288  									v1.ResourceName("testdevice"): resource.MustParse("6"),
  1289  								},
  1290  							},
  1291  						},
  1292  					},
  1293  				},
  1294  			},
  1295  			devices: map[string][]pluginapi.Device{
  1296  				"testdevice": {
  1297  					makeNUMADevice("Dev1", 0),
  1298  					makeNUMADevice("Dev2", 0),
  1299  					makeNUMADevice("Dev3", 1),
  1300  					makeNUMADevice("Dev4", 1),
  1301  				},
  1302  			},
  1303  			expectedHints: map[string][]topologymanager.TopologyHint{
  1304  				"testdevice": {},
  1305  			},
  1306  		},
  1307  		{
  1308  			description: "Single device type, all already allocated to container",
  1309  			pod: &v1.Pod{
  1310  				ObjectMeta: metav1.ObjectMeta{
  1311  					UID: "fakePod",
  1312  				},
  1313  				Spec: v1.PodSpec{
  1314  					Containers: []v1.Container{
  1315  						{
  1316  							Name: "fakeContainer",
  1317  							Resources: v1.ResourceRequirements{
  1318  								Limits: v1.ResourceList{
  1319  									v1.ResourceName("testdevice"): resource.MustParse("2"),
  1320  								},
  1321  							},
  1322  						},
  1323  					},
  1324  				},
  1325  			},
  1326  			devices: map[string][]pluginapi.Device{
  1327  				"testdevice": {
  1328  					makeNUMADevice("Dev1", 0),
  1329  					makeNUMADevice("Dev2", 0),
  1330  				},
  1331  			},
  1332  			allocatedDevices: map[string]map[string]map[string][]string{
  1333  				"fakePod": {
  1334  					"fakeContainer": {
  1335  						"testdevice": {"Dev1", "Dev2"},
  1336  					},
  1337  				},
  1338  			},
  1339  			expectedHints: map[string][]topologymanager.TopologyHint{
  1340  				"testdevice": {
  1341  					{
  1342  						NUMANodeAffinity: makeSocketMask(0),
  1343  						Preferred:        true,
  1344  					},
  1345  					{
  1346  						NUMANodeAffinity: makeSocketMask(0, 1),
  1347  						Preferred:        false,
  1348  					},
  1349  				},
  1350  			},
  1351  		},
  1352  		{
  1353  			description: "Single device type, less already allocated to container than requested",
  1354  			pod: &v1.Pod{
  1355  				ObjectMeta: metav1.ObjectMeta{
  1356  					UID: "fakePod",
  1357  				},
  1358  				Spec: v1.PodSpec{
  1359  					Containers: []v1.Container{
  1360  						{
  1361  							Name: "fakeContainer",
  1362  							Resources: v1.ResourceRequirements{
  1363  								Limits: v1.ResourceList{
  1364  									v1.ResourceName("testdevice"): resource.MustParse("4"),
  1365  								},
  1366  							},
  1367  						},
  1368  					},
  1369  				},
  1370  			},
  1371  			devices: map[string][]pluginapi.Device{
  1372  				"testdevice": {
  1373  					makeNUMADevice("Dev1", 0),
  1374  					makeNUMADevice("Dev2", 0),
  1375  					makeNUMADevice("Dev3", 1),
  1376  					makeNUMADevice("Dev4", 1),
  1377  				},
  1378  			},
  1379  			allocatedDevices: map[string]map[string]map[string][]string{
  1380  				"fakePod": {
  1381  					"fakeContainer": {
  1382  						"testdevice": {"Dev1", "Dev2"},
  1383  					},
  1384  				},
  1385  			},
  1386  			expectedHints: map[string][]topologymanager.TopologyHint{
  1387  				"testdevice": {},
  1388  			},
  1389  		},
  1390  		{
  1391  			description: "Single device type, more already allocated to container than requested",
  1392  			pod: &v1.Pod{
  1393  				ObjectMeta: metav1.ObjectMeta{
  1394  					UID: "fakePod",
  1395  				},
  1396  				Spec: v1.PodSpec{
  1397  					Containers: []v1.Container{
  1398  						{
  1399  							Name: "fakeContainer",
  1400  							Resources: v1.ResourceRequirements{
  1401  								Limits: v1.ResourceList{
  1402  									v1.ResourceName("testdevice"): resource.MustParse("2"),
  1403  								},
  1404  							},
  1405  						},
  1406  					},
  1407  				},
  1408  			},
  1409  			devices: map[string][]pluginapi.Device{
  1410  				"testdevice": {
  1411  					makeNUMADevice("Dev1", 0),
  1412  					makeNUMADevice("Dev2", 0),
  1413  					makeNUMADevice("Dev3", 1),
  1414  					makeNUMADevice("Dev4", 1),
  1415  				},
  1416  			},
  1417  			allocatedDevices: map[string]map[string]map[string][]string{
  1418  				"fakePod": {
  1419  					"fakeContainer": {
  1420  						"testdevice": {"Dev1", "Dev2", "Dev3", "Dev4"},
  1421  					},
  1422  				},
  1423  			},
  1424  			expectedHints: map[string][]topologymanager.TopologyHint{
  1425  				"testdevice": {},
  1426  			},
  1427  		},
  1428  	}
  1429  }
  1430  
  1431  func getPodScopeTestCases() []topologyHintTestCase {
  1432  	return []topologyHintTestCase{
  1433  		{
  1434  			description: "2 device types, user container only",
  1435  			pod: &v1.Pod{
  1436  				ObjectMeta: metav1.ObjectMeta{
  1437  					UID: "fakePod",
  1438  				},
  1439  				Spec: v1.PodSpec{
  1440  					Containers: []v1.Container{
  1441  						{
  1442  							Name: "fakeContainer1",
  1443  							Resources: v1.ResourceRequirements{
  1444  								Limits: v1.ResourceList{
  1445  									v1.ResourceName("testdevice1"): resource.MustParse("2"),
  1446  								},
  1447  							},
  1448  						},
  1449  						{
  1450  							Name: "fakeContainer2",
  1451  							Resources: v1.ResourceRequirements{
  1452  								Limits: v1.ResourceList{
  1453  									v1.ResourceName("testdevice2"): resource.MustParse("2"),
  1454  								},
  1455  							},
  1456  						},
  1457  						{
  1458  							Name: "fakeContainer3",
  1459  							Resources: v1.ResourceRequirements{
  1460  								Limits: v1.ResourceList{
  1461  									v1.ResourceName("notRegistered"): resource.MustParse("2"),
  1462  								},
  1463  							},
  1464  						},
  1465  					},
  1466  				},
  1467  			},
  1468  			devices: map[string][]pluginapi.Device{
  1469  				"testdevice1": {
  1470  					makeNUMADevice("Dev1", 0),
  1471  					makeNUMADevice("Dev2", 0),
  1472  					makeNUMADevice("Dev3", 1),
  1473  					makeNUMADevice("Dev4", 1),
  1474  				},
  1475  				"testdevice2": {
  1476  					makeNUMADevice("Dev1", 0),
  1477  					makeNUMADevice("Dev2", 0),
  1478  					makeNUMADevice("Dev3", 1),
  1479  					makeNUMADevice("Dev4", 1),
  1480  				},
  1481  			},
  1482  			expectedHints: map[string][]topologymanager.TopologyHint{
  1483  				"testdevice1": {
  1484  					{
  1485  						NUMANodeAffinity: makeSocketMask(0),
  1486  						Preferred:        true,
  1487  					},
  1488  					{
  1489  						NUMANodeAffinity: makeSocketMask(1),
  1490  						Preferred:        true,
  1491  					},
  1492  					{
  1493  						NUMANodeAffinity: makeSocketMask(0, 1),
  1494  						Preferred:        false,
  1495  					},
  1496  				},
  1497  				"testdevice2": {
  1498  					{
  1499  						NUMANodeAffinity: makeSocketMask(0),
  1500  						Preferred:        true,
  1501  					},
  1502  					{
  1503  						NUMANodeAffinity: makeSocketMask(1),
  1504  						Preferred:        true,
  1505  					},
  1506  					{
  1507  						NUMANodeAffinity: makeSocketMask(0, 1),
  1508  						Preferred:        false,
  1509  					},
  1510  				},
  1511  			},
  1512  		},
  1513  		{
  1514  			description: "2 device types, request resources for init containers and user container",
  1515  			pod: &v1.Pod{
  1516  				ObjectMeta: metav1.ObjectMeta{
  1517  					UID: "fakePod",
  1518  				},
  1519  				Spec: v1.PodSpec{
  1520  					InitContainers: []v1.Container{
  1521  						{
  1522  							Resources: v1.ResourceRequirements{
  1523  								Limits: v1.ResourceList{
  1524  									v1.ResourceName("testdevice1"): resource.MustParse("1"),
  1525  									v1.ResourceName("testdevice2"): resource.MustParse("1"),
  1526  								},
  1527  							},
  1528  						},
  1529  						{
  1530  							Resources: v1.ResourceRequirements{
  1531  								Limits: v1.ResourceList{
  1532  									v1.ResourceName("testdevice1"): resource.MustParse("1"),
  1533  									v1.ResourceName("testdevice2"): resource.MustParse("2"),
  1534  								},
  1535  							},
  1536  						},
  1537  					},
  1538  					Containers: []v1.Container{
  1539  						{
  1540  							Name: "fakeContainer1",
  1541  							Resources: v1.ResourceRequirements{
  1542  								Limits: v1.ResourceList{
  1543  									v1.ResourceName("testdevice1"): resource.MustParse("1"),
  1544  									v1.ResourceName("testdevice2"): resource.MustParse("1"),
  1545  								},
  1546  							},
  1547  						},
  1548  						{
  1549  							Name: "fakeContainer2",
  1550  							Resources: v1.ResourceRequirements{
  1551  								Limits: v1.ResourceList{
  1552  									v1.ResourceName("testdevice1"): resource.MustParse("1"),
  1553  									v1.ResourceName("testdevice2"): resource.MustParse("1"),
  1554  								},
  1555  							},
  1556  						},
  1557  						{
  1558  							Name: "fakeContainer3",
  1559  							Resources: v1.ResourceRequirements{
  1560  								Limits: v1.ResourceList{
  1561  									v1.ResourceName("notRegistered"): resource.MustParse("1"),
  1562  								},
  1563  							},
  1564  						},
  1565  					},
  1566  				},
  1567  			},
  1568  			devices: map[string][]pluginapi.Device{
  1569  				"testdevice1": {
  1570  					makeNUMADevice("Dev1", 0),
  1571  					makeNUMADevice("Dev2", 0),
  1572  					makeNUMADevice("Dev3", 1),
  1573  					makeNUMADevice("Dev4", 1),
  1574  				},
  1575  				"testdevice2": {
  1576  					makeNUMADevice("Dev1", 0),
  1577  					makeNUMADevice("Dev2", 0),
  1578  					makeNUMADevice("Dev3", 1),
  1579  					makeNUMADevice("Dev4", 1),
  1580  				},
  1581  			},
  1582  			expectedHints: map[string][]topologymanager.TopologyHint{
  1583  				"testdevice1": {
  1584  					{
  1585  						NUMANodeAffinity: makeSocketMask(0),
  1586  						Preferred:        true,
  1587  					},
  1588  					{
  1589  						NUMANodeAffinity: makeSocketMask(1),
  1590  						Preferred:        true,
  1591  					},
  1592  					{
  1593  						NUMANodeAffinity: makeSocketMask(0, 1),
  1594  						Preferred:        false,
  1595  					},
  1596  				},
  1597  				"testdevice2": {
  1598  					{
  1599  						NUMANodeAffinity: makeSocketMask(0),
  1600  						Preferred:        true,
  1601  					},
  1602  					{
  1603  						NUMANodeAffinity: makeSocketMask(1),
  1604  						Preferred:        true,
  1605  					},
  1606  					{
  1607  						NUMANodeAffinity: makeSocketMask(0, 1),
  1608  						Preferred:        false,
  1609  					},
  1610  				},
  1611  			},
  1612  		},
  1613  		{
  1614  			description: "2 device types, user container only, optimal on 1 NUMA node, forced cross-NUMA",
  1615  			pod: &v1.Pod{
  1616  				ObjectMeta: metav1.ObjectMeta{
  1617  					UID: "fakePod",
  1618  				},
  1619  				Spec: v1.PodSpec{
  1620  					Containers: []v1.Container{
  1621  						{
  1622  							Name: "fakeContainer1",
  1623  							Resources: v1.ResourceRequirements{
  1624  								Limits: v1.ResourceList{
  1625  									v1.ResourceName("testdevice1"): resource.MustParse("1"),
  1626  									v1.ResourceName("testdevice2"): resource.MustParse("1"),
  1627  								},
  1628  							},
  1629  						},
  1630  						{
  1631  							Name: "fakeContainer2",
  1632  							Resources: v1.ResourceRequirements{
  1633  								Limits: v1.ResourceList{
  1634  									v1.ResourceName("testdevice1"): resource.MustParse("1"),
  1635  									v1.ResourceName("testdevice2"): resource.MustParse("1"),
  1636  								},
  1637  							},
  1638  						},
  1639  						{
  1640  							Name: "fakeContainer3",
  1641  							Resources: v1.ResourceRequirements{
  1642  								Limits: v1.ResourceList{
  1643  									v1.ResourceName("notRegistered"): resource.MustParse("1"),
  1644  								},
  1645  							},
  1646  						},
  1647  					},
  1648  				},
  1649  			},
  1650  			devices: map[string][]pluginapi.Device{
  1651  				"testdevice1": {
  1652  					makeNUMADevice("Dev1", 0),
  1653  					makeNUMADevice("Dev2", 0),
  1654  					makeNUMADevice("Dev3", 1),
  1655  					makeNUMADevice("Dev4", 1),
  1656  				},
  1657  				"testdevice2": {
  1658  					makeNUMADevice("Dev1", 0),
  1659  					makeNUMADevice("Dev2", 0),
  1660  					makeNUMADevice("Dev3", 1),
  1661  					makeNUMADevice("Dev4", 1),
  1662  				},
  1663  			},
  1664  			allocatedDevices: map[string]map[string]map[string][]string{
  1665  				"fakeOtherPod": {
  1666  					"fakeOtherContainer": {
  1667  						"testdevice1": {"Dev1", "Dev3"},
  1668  						"testdevice2": {"Dev1", "Dev3"},
  1669  					},
  1670  				},
  1671  			},
  1672  			expectedHints: map[string][]topologymanager.TopologyHint{
  1673  				"testdevice1": {
  1674  					{
  1675  						NUMANodeAffinity: makeSocketMask(0, 1),
  1676  						Preferred:        false,
  1677  					},
  1678  				},
  1679  				"testdevice2": {
  1680  					{
  1681  						NUMANodeAffinity: makeSocketMask(0, 1),
  1682  						Preferred:        false,
  1683  					},
  1684  				},
  1685  			},
  1686  		},
  1687  	}
  1688  }
  1689  

View as plain text