k8s.io/kubernetes@v1.29.3/pkg/kubelet/cm/devicemanager/manager_test.go (about)

     1  /*
     2  Copyright 2017 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  	"os"
    22  	"path/filepath"
    23  	"reflect"
    24  	goruntime "runtime"
    25  	"sync"
    26  	"sync/atomic"
    27  	"testing"
    28  	"time"
    29  
    30  	cadvisorapi "github.com/google/cadvisor/info/v1"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  	v1 "k8s.io/api/core/v1"
    34  	"k8s.io/apimachinery/pkg/api/resource"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/types"
    37  	"k8s.io/apimachinery/pkg/util/sets"
    38  	"k8s.io/apimachinery/pkg/util/uuid"
    39  	"k8s.io/apimachinery/pkg/util/wait"
    40  	"k8s.io/client-go/tools/record"
    41  	pluginapi "k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1"
    42  	watcherapi "k8s.io/kubelet/pkg/apis/pluginregistration/v1"
    43  	"k8s.io/kubernetes/pkg/kubelet/checkpointmanager"
    44  	"k8s.io/kubernetes/pkg/kubelet/cm/containermap"
    45  	"k8s.io/kubernetes/pkg/kubelet/cm/devicemanager/checkpoint"
    46  	plugin "k8s.io/kubernetes/pkg/kubelet/cm/devicemanager/plugin/v1beta1"
    47  	"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager"
    48  	"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
    49  	"k8s.io/kubernetes/pkg/kubelet/config"
    50  	"k8s.io/kubernetes/pkg/kubelet/lifecycle"
    51  	"k8s.io/kubernetes/pkg/kubelet/pluginmanager"
    52  	schedulerframework "k8s.io/kubernetes/pkg/scheduler/framework"
    53  )
    54  
    55  const (
    56  	testResourceName = "fake-domain/resource"
    57  )
    58  
    59  func newWrappedManagerImpl(socketPath string, manager *ManagerImpl) *wrappedManagerImpl {
    60  	w := &wrappedManagerImpl{
    61  		ManagerImpl: manager,
    62  		callback:    manager.genericDeviceUpdateCallback,
    63  	}
    64  	w.socketdir, _ = filepath.Split(socketPath)
    65  	w.server, _ = plugin.NewServer(socketPath, w, w)
    66  	return w
    67  }
    68  
    69  type wrappedManagerImpl struct {
    70  	*ManagerImpl
    71  	socketdir string
    72  	callback  func(string, []pluginapi.Device)
    73  }
    74  
    75  func (m *wrappedManagerImpl) PluginListAndWatchReceiver(r string, resp *pluginapi.ListAndWatchResponse) {
    76  	var devices []pluginapi.Device
    77  	for _, d := range resp.Devices {
    78  		devices = append(devices, *d)
    79  	}
    80  	m.callback(r, devices)
    81  }
    82  
    83  func tmpSocketDir() (socketDir, socketName, pluginSocketName string, err error) {
    84  	socketDir, err = os.MkdirTemp("", "device_plugin")
    85  	if err != nil {
    86  		return
    87  	}
    88  	socketName = filepath.Join(socketDir, "server.sock")
    89  	pluginSocketName = filepath.Join(socketDir, "device-plugin.sock")
    90  	os.MkdirAll(socketDir, 0755)
    91  	return
    92  }
    93  
    94  func TestNewManagerImpl(t *testing.T) {
    95  	socketDir, socketName, _, err := tmpSocketDir()
    96  	topologyStore := topologymanager.NewFakeManager()
    97  	require.NoError(t, err)
    98  	defer os.RemoveAll(socketDir)
    99  	_, err = newManagerImpl(socketName, nil, topologyStore)
   100  	require.NoError(t, err)
   101  	os.RemoveAll(socketDir)
   102  }
   103  
   104  func TestNewManagerImplStart(t *testing.T) {
   105  	socketDir, socketName, pluginSocketName, err := tmpSocketDir()
   106  	require.NoError(t, err)
   107  	defer os.RemoveAll(socketDir)
   108  	m, _, p := setup(t, []*pluginapi.Device{}, func(n string, d []pluginapi.Device) {}, socketName, pluginSocketName)
   109  	cleanup(t, m, p)
   110  	// Stop should tolerate being called more than once.
   111  	cleanup(t, m, p)
   112  }
   113  
   114  func TestNewManagerImplStartProbeMode(t *testing.T) {
   115  	socketDir, socketName, pluginSocketName, err := tmpSocketDir()
   116  	require.NoError(t, err)
   117  	defer os.RemoveAll(socketDir)
   118  	m, _, p, _ := setupInProbeMode(t, []*pluginapi.Device{}, func(n string, d []pluginapi.Device) {}, socketName, pluginSocketName)
   119  	cleanup(t, m, p)
   120  }
   121  
   122  // Tests that the device plugin manager correctly handles registration and re-registration by
   123  // making sure that after registration, devices are correctly updated and if a re-registration
   124  // happens, we will NOT delete devices; and no orphaned devices left.
   125  func TestDevicePluginReRegistration(t *testing.T) {
   126  	// TODO: Remove skip once https://github.com/kubernetes/kubernetes/pull/115269 merges.
   127  	if goruntime.GOOS == "windows" {
   128  		t.Skip("Skipping test on Windows.")
   129  	}
   130  	socketDir, socketName, pluginSocketName, err := tmpSocketDir()
   131  	require.NoError(t, err)
   132  	defer os.RemoveAll(socketDir)
   133  	devs := []*pluginapi.Device{
   134  		{ID: "Dev1", Health: pluginapi.Healthy},
   135  		{ID: "Dev2", Health: pluginapi.Healthy},
   136  	}
   137  	devsForRegistration := []*pluginapi.Device{
   138  		{ID: "Dev3", Health: pluginapi.Healthy},
   139  	}
   140  	for _, preStartContainerFlag := range []bool{false, true} {
   141  		for _, getPreferredAllocationFlag := range []bool{false, true} {
   142  			m, ch, p1 := setup(t, devs, nil, socketName, pluginSocketName)
   143  			p1.Register(socketName, testResourceName, "")
   144  
   145  			select {
   146  			case <-ch:
   147  			case <-time.After(5 * time.Second):
   148  				t.Fatalf("timeout while waiting for manager update")
   149  			}
   150  			capacity, allocatable, _ := m.GetCapacity()
   151  			resourceCapacity := capacity[v1.ResourceName(testResourceName)]
   152  			resourceAllocatable := allocatable[v1.ResourceName(testResourceName)]
   153  			require.Equal(t, resourceCapacity.Value(), resourceAllocatable.Value(), "capacity should equal to allocatable")
   154  			require.Equal(t, int64(2), resourceAllocatable.Value(), "Devices are not updated.")
   155  
   156  			p2 := plugin.NewDevicePluginStub(devs, pluginSocketName+".new", testResourceName, preStartContainerFlag, getPreferredAllocationFlag)
   157  			err = p2.Start()
   158  			require.NoError(t, err)
   159  			p2.Register(socketName, testResourceName, "")
   160  
   161  			select {
   162  			case <-ch:
   163  			case <-time.After(5 * time.Second):
   164  				t.Fatalf("timeout while waiting for manager update")
   165  			}
   166  			capacity, allocatable, _ = m.GetCapacity()
   167  			resourceCapacity = capacity[v1.ResourceName(testResourceName)]
   168  			resourceAllocatable = allocatable[v1.ResourceName(testResourceName)]
   169  			require.Equal(t, resourceCapacity.Value(), resourceAllocatable.Value(), "capacity should equal to allocatable")
   170  			require.Equal(t, int64(2), resourceAllocatable.Value(), "Devices shouldn't change.")
   171  
   172  			// Test the scenario that a plugin re-registers with different devices.
   173  			p3 := plugin.NewDevicePluginStub(devsForRegistration, pluginSocketName+".third", testResourceName, preStartContainerFlag, getPreferredAllocationFlag)
   174  			err = p3.Start()
   175  			require.NoError(t, err)
   176  			p3.Register(socketName, testResourceName, "")
   177  
   178  			select {
   179  			case <-ch:
   180  			case <-time.After(5 * time.Second):
   181  				t.Fatalf("timeout while waiting for manager update")
   182  			}
   183  			capacity, allocatable, _ = m.GetCapacity()
   184  			resourceCapacity = capacity[v1.ResourceName(testResourceName)]
   185  			resourceAllocatable = allocatable[v1.ResourceName(testResourceName)]
   186  			require.Equal(t, resourceCapacity.Value(), resourceAllocatable.Value(), "capacity should equal to allocatable")
   187  			require.Equal(t, int64(1), resourceAllocatable.Value(), "Devices of plugin previously registered should be removed.")
   188  			p2.Stop()
   189  			p3.Stop()
   190  			cleanup(t, m, p1)
   191  		}
   192  	}
   193  }
   194  
   195  // Tests that the device plugin manager correctly handles registration and re-registration by
   196  // making sure that after registration, devices are correctly updated and if a re-registration
   197  // happens, we will NOT delete devices; and no orphaned devices left.
   198  // While testing above scenario, plugin discovery and registration will be done using
   199  // Kubelet probe based mechanism
   200  func TestDevicePluginReRegistrationProbeMode(t *testing.T) {
   201  	// TODO: Remove skip once https://github.com/kubernetes/kubernetes/pull/115269 merges.
   202  	if goruntime.GOOS == "windows" {
   203  		t.Skip("Skipping test on Windows.")
   204  	}
   205  	socketDir, socketName, pluginSocketName, err := tmpSocketDir()
   206  	require.NoError(t, err)
   207  	defer os.RemoveAll(socketDir)
   208  	devs := []*pluginapi.Device{
   209  		{ID: "Dev1", Health: pluginapi.Healthy},
   210  		{ID: "Dev2", Health: pluginapi.Healthy},
   211  	}
   212  	devsForRegistration := []*pluginapi.Device{
   213  		{ID: "Dev3", Health: pluginapi.Healthy},
   214  	}
   215  
   216  	m, ch, p1, _ := setupInProbeMode(t, devs, nil, socketName, pluginSocketName)
   217  
   218  	// Wait for the first callback to be issued.
   219  	select {
   220  	case <-ch:
   221  	case <-time.After(5 * time.Second):
   222  		t.FailNow()
   223  	}
   224  	capacity, allocatable, _ := m.GetCapacity()
   225  	resourceCapacity := capacity[v1.ResourceName(testResourceName)]
   226  	resourceAllocatable := allocatable[v1.ResourceName(testResourceName)]
   227  	require.Equal(t, resourceCapacity.Value(), resourceAllocatable.Value(), "capacity should equal to allocatable")
   228  	require.Equal(t, int64(2), resourceAllocatable.Value(), "Devices are not updated.")
   229  
   230  	p2 := plugin.NewDevicePluginStub(devs, pluginSocketName+".new", testResourceName, false, false)
   231  	err = p2.Start()
   232  	require.NoError(t, err)
   233  	// Wait for the second callback to be issued.
   234  	select {
   235  	case <-ch:
   236  	case <-time.After(5 * time.Second):
   237  		t.FailNow()
   238  	}
   239  
   240  	capacity, allocatable, _ = m.GetCapacity()
   241  	resourceCapacity = capacity[v1.ResourceName(testResourceName)]
   242  	resourceAllocatable = allocatable[v1.ResourceName(testResourceName)]
   243  	require.Equal(t, resourceCapacity.Value(), resourceAllocatable.Value(), "capacity should equal to allocatable")
   244  	require.Equal(t, int64(2), resourceAllocatable.Value(), "Devices are not updated.")
   245  
   246  	// Test the scenario that a plugin re-registers with different devices.
   247  	p3 := plugin.NewDevicePluginStub(devsForRegistration, pluginSocketName+".third", testResourceName, false, false)
   248  	err = p3.Start()
   249  	require.NoError(t, err)
   250  	// Wait for the third callback to be issued.
   251  	select {
   252  	case <-ch:
   253  	case <-time.After(5 * time.Second):
   254  		t.FailNow()
   255  	}
   256  
   257  	capacity, allocatable, _ = m.GetCapacity()
   258  	resourceCapacity = capacity[v1.ResourceName(testResourceName)]
   259  	resourceAllocatable = allocatable[v1.ResourceName(testResourceName)]
   260  	require.Equal(t, resourceCapacity.Value(), resourceAllocatable.Value(), "capacity should equal to allocatable")
   261  	require.Equal(t, int64(1), resourceAllocatable.Value(), "Devices of previous registered should be removed")
   262  	p2.Stop()
   263  	p3.Stop()
   264  	cleanup(t, m, p1)
   265  }
   266  
   267  func setupDeviceManager(t *testing.T, devs []*pluginapi.Device, callback monitorCallback, socketName string,
   268  	topology []cadvisorapi.Node) (Manager, <-chan interface{}) {
   269  	topologyStore := topologymanager.NewFakeManager()
   270  	m, err := newManagerImpl(socketName, topology, topologyStore)
   271  	require.NoError(t, err)
   272  	updateChan := make(chan interface{})
   273  
   274  	w := newWrappedManagerImpl(socketName, m)
   275  	if callback != nil {
   276  		w.callback = callback
   277  	}
   278  
   279  	originalCallback := w.callback
   280  	w.callback = func(resourceName string, devices []pluginapi.Device) {
   281  		originalCallback(resourceName, devices)
   282  		updateChan <- new(interface{})
   283  	}
   284  	activePods := func() []*v1.Pod {
   285  		return []*v1.Pod{}
   286  	}
   287  
   288  	// test steady state, initialization where sourcesReady, containerMap and containerRunningSet
   289  	// are relevant will be tested with a different flow
   290  	err = w.Start(activePods, &sourcesReadyStub{}, containermap.NewContainerMap(), sets.New[string]())
   291  	require.NoError(t, err)
   292  
   293  	return w, updateChan
   294  }
   295  
   296  func setupDevicePlugin(t *testing.T, devs []*pluginapi.Device, pluginSocketName string) *plugin.Stub {
   297  	p := plugin.NewDevicePluginStub(devs, pluginSocketName, testResourceName, false, false)
   298  	err := p.Start()
   299  	require.NoError(t, err)
   300  	return p
   301  }
   302  
   303  func setupPluginManager(t *testing.T, pluginSocketName string, m Manager) pluginmanager.PluginManager {
   304  	pluginManager := pluginmanager.NewPluginManager(
   305  		filepath.Dir(pluginSocketName), /* sockDir */
   306  		&record.FakeRecorder{},
   307  	)
   308  
   309  	runPluginManager(pluginManager)
   310  	pluginManager.AddHandler(watcherapi.DevicePlugin, m.GetWatcherHandler())
   311  	return pluginManager
   312  }
   313  
   314  func runPluginManager(pluginManager pluginmanager.PluginManager) {
   315  	// FIXME: Replace sets.String with sets.Set[string]
   316  	sourcesReady := config.NewSourcesReady(func(_ sets.String) bool { return true })
   317  	go pluginManager.Run(sourcesReady, wait.NeverStop)
   318  }
   319  
   320  func setup(t *testing.T, devs []*pluginapi.Device, callback monitorCallback, socketName string, pluginSocketName string) (Manager, <-chan interface{}, *plugin.Stub) {
   321  	m, updateChan := setupDeviceManager(t, devs, callback, socketName, nil)
   322  	p := setupDevicePlugin(t, devs, pluginSocketName)
   323  	return m, updateChan, p
   324  }
   325  
   326  func setupInProbeMode(t *testing.T, devs []*pluginapi.Device, callback monitorCallback, socketName string, pluginSocketName string) (Manager, <-chan interface{}, *plugin.Stub, pluginmanager.PluginManager) {
   327  	m, updateChan := setupDeviceManager(t, devs, callback, socketName, nil)
   328  	p := setupDevicePlugin(t, devs, pluginSocketName)
   329  	pm := setupPluginManager(t, pluginSocketName, m)
   330  	return m, updateChan, p, pm
   331  }
   332  
   333  func cleanup(t *testing.T, m Manager, p *plugin.Stub) {
   334  	p.Stop()
   335  	m.Stop()
   336  }
   337  
   338  func TestUpdateCapacityAllocatable(t *testing.T) {
   339  	socketDir, socketName, _, err := tmpSocketDir()
   340  	topologyStore := topologymanager.NewFakeManager()
   341  	require.NoError(t, err)
   342  	defer os.RemoveAll(socketDir)
   343  	testManager, err := newManagerImpl(socketName, nil, topologyStore)
   344  	as := assert.New(t)
   345  	as.NotNil(testManager)
   346  	as.Nil(err)
   347  
   348  	devs := []pluginapi.Device{
   349  		{ID: "Device1", Health: pluginapi.Healthy},
   350  		{ID: "Device2", Health: pluginapi.Healthy},
   351  		{ID: "Device3", Health: pluginapi.Unhealthy},
   352  	}
   353  	callback := testManager.genericDeviceUpdateCallback
   354  
   355  	// Adds three devices for resource1, two healthy and one unhealthy.
   356  	// Expects capacity for resource1 to be 2.
   357  	resourceName1 := "domain1.com/resource1"
   358  	e1 := &endpointImpl{}
   359  	testManager.endpoints[resourceName1] = endpointInfo{e: e1, opts: nil}
   360  	callback(resourceName1, devs)
   361  	capacity, allocatable, removedResources := testManager.GetCapacity()
   362  	resource1Capacity, ok := capacity[v1.ResourceName(resourceName1)]
   363  	as.True(ok)
   364  	resource1Allocatable, ok := allocatable[v1.ResourceName(resourceName1)]
   365  	as.True(ok)
   366  	as.Equal(int64(3), resource1Capacity.Value())
   367  	as.Equal(int64(2), resource1Allocatable.Value())
   368  	as.Equal(0, len(removedResources))
   369  
   370  	// Deletes an unhealthy device should NOT change allocatable but change capacity.
   371  	devs1 := devs[:len(devs)-1]
   372  	callback(resourceName1, devs1)
   373  	capacity, allocatable, removedResources = testManager.GetCapacity()
   374  	resource1Capacity, ok = capacity[v1.ResourceName(resourceName1)]
   375  	as.True(ok)
   376  	resource1Allocatable, ok = allocatable[v1.ResourceName(resourceName1)]
   377  	as.True(ok)
   378  	as.Equal(int64(2), resource1Capacity.Value())
   379  	as.Equal(int64(2), resource1Allocatable.Value())
   380  	as.Equal(0, len(removedResources))
   381  
   382  	// Updates a healthy device to unhealthy should reduce allocatable by 1.
   383  	devs[1].Health = pluginapi.Unhealthy
   384  	callback(resourceName1, devs)
   385  	capacity, allocatable, removedResources = testManager.GetCapacity()
   386  	resource1Capacity, ok = capacity[v1.ResourceName(resourceName1)]
   387  	as.True(ok)
   388  	resource1Allocatable, ok = allocatable[v1.ResourceName(resourceName1)]
   389  	as.True(ok)
   390  	as.Equal(int64(3), resource1Capacity.Value())
   391  	as.Equal(int64(1), resource1Allocatable.Value())
   392  	as.Equal(0, len(removedResources))
   393  
   394  	// Deletes a healthy device should reduce capacity and allocatable by 1.
   395  	devs2 := devs[1:]
   396  	callback(resourceName1, devs2)
   397  	capacity, allocatable, removedResources = testManager.GetCapacity()
   398  	resource1Capacity, ok = capacity[v1.ResourceName(resourceName1)]
   399  	as.True(ok)
   400  	resource1Allocatable, ok = allocatable[v1.ResourceName(resourceName1)]
   401  	as.True(ok)
   402  	as.Equal(int64(0), resource1Allocatable.Value())
   403  	as.Equal(int64(2), resource1Capacity.Value())
   404  	as.Equal(0, len(removedResources))
   405  
   406  	// Tests adding another resource.
   407  	resourceName2 := "resource2"
   408  	e2 := &endpointImpl{}
   409  	e2.client = plugin.NewPluginClient(resourceName2, socketName, testManager)
   410  	testManager.endpoints[resourceName2] = endpointInfo{e: e2, opts: nil}
   411  	callback(resourceName2, devs)
   412  	capacity, allocatable, removedResources = testManager.GetCapacity()
   413  	as.Equal(2, len(capacity))
   414  	resource2Capacity, ok := capacity[v1.ResourceName(resourceName2)]
   415  	as.True(ok)
   416  	resource2Allocatable, ok := allocatable[v1.ResourceName(resourceName2)]
   417  	as.True(ok)
   418  	as.Equal(int64(3), resource2Capacity.Value())
   419  	as.Equal(int64(1), resource2Allocatable.Value())
   420  	as.Equal(0, len(removedResources))
   421  
   422  	// Expires resourceName1 endpoint. Verifies testManager.GetCapacity() reports that resourceName1
   423  	// is removed from capacity and it no longer exists in healthyDevices after the call.
   424  	e1.setStopTime(time.Now().Add(-1*endpointStopGracePeriod - time.Duration(10)*time.Second))
   425  	capacity, allocatable, removed := testManager.GetCapacity()
   426  	as.Equal([]string{resourceName1}, removed)
   427  	as.NotContains(capacity, v1.ResourceName(resourceName1))
   428  	as.NotContains(allocatable, v1.ResourceName(resourceName1))
   429  	val, ok := capacity[v1.ResourceName(resourceName2)]
   430  	as.True(ok)
   431  	as.Equal(int64(3), val.Value())
   432  	as.NotContains(testManager.healthyDevices, resourceName1)
   433  	as.NotContains(testManager.unhealthyDevices, resourceName1)
   434  	as.NotContains(testManager.endpoints, resourceName1)
   435  	as.Equal(1, len(testManager.endpoints))
   436  
   437  	// Stops resourceName2 endpoint. Verifies its stopTime is set, allocate and
   438  	// preStartContainer calls return errors.
   439  	e2.client.Disconnect()
   440  	as.False(e2.stopTime.IsZero())
   441  	_, err = e2.allocate([]string{"Device1"})
   442  	reflect.DeepEqual(err, fmt.Errorf(errEndpointStopped, e2))
   443  	_, err = e2.preStartContainer([]string{"Device1"})
   444  	reflect.DeepEqual(err, fmt.Errorf(errEndpointStopped, e2))
   445  	// Marks resourceName2 unhealthy and verifies its capacity/allocatable are
   446  	// correctly updated.
   447  	testManager.markResourceUnhealthy(resourceName2)
   448  	capacity, allocatable, removed = testManager.GetCapacity()
   449  	val, ok = capacity[v1.ResourceName(resourceName2)]
   450  	as.True(ok)
   451  	as.Equal(int64(3), val.Value())
   452  	val, ok = allocatable[v1.ResourceName(resourceName2)]
   453  	as.True(ok)
   454  	as.Equal(int64(0), val.Value())
   455  	as.Empty(removed)
   456  	// Writes and re-reads checkpoints. Verifies we create a stopped endpoint
   457  	// for resourceName2, its capacity is set to zero, and we still consider
   458  	// it as a DevicePlugin resource. This makes sure any pod that was scheduled
   459  	// during the time of propagating capacity change to the scheduler will be
   460  	// properly rejected instead of being incorrectly started.
   461  	err = testManager.writeCheckpoint()
   462  	as.Nil(err)
   463  	testManager.healthyDevices = make(map[string]sets.Set[string])
   464  	testManager.unhealthyDevices = make(map[string]sets.Set[string])
   465  	err = testManager.readCheckpoint()
   466  	as.Nil(err)
   467  	as.Equal(1, len(testManager.endpoints))
   468  	as.Contains(testManager.endpoints, resourceName2)
   469  	capacity, allocatable, removed = testManager.GetCapacity()
   470  	val, ok = capacity[v1.ResourceName(resourceName2)]
   471  	as.True(ok)
   472  	as.Equal(int64(0), val.Value())
   473  	val, ok = allocatable[v1.ResourceName(resourceName2)]
   474  	as.True(ok)
   475  	as.Equal(int64(0), val.Value())
   476  	as.Empty(removed)
   477  	as.True(testManager.isDevicePluginResource(resourceName2))
   478  }
   479  
   480  func TestGetAllocatableDevicesMultipleResources(t *testing.T) {
   481  	socketDir, socketName, _, err := tmpSocketDir()
   482  	topologyStore := topologymanager.NewFakeManager()
   483  	require.NoError(t, err)
   484  	defer os.RemoveAll(socketDir)
   485  	testManager, err := newManagerImpl(socketName, nil, topologyStore)
   486  	as := assert.New(t)
   487  	as.NotNil(testManager)
   488  	as.Nil(err)
   489  
   490  	resource1Devs := []pluginapi.Device{
   491  		{ID: "R1Device1", Health: pluginapi.Healthy},
   492  		{ID: "R1Device2", Health: pluginapi.Healthy},
   493  		{ID: "R1Device3", Health: pluginapi.Unhealthy},
   494  	}
   495  	resourceName1 := "domain1.com/resource1"
   496  	e1 := &endpointImpl{}
   497  	testManager.endpoints[resourceName1] = endpointInfo{e: e1, opts: nil}
   498  	testManager.genericDeviceUpdateCallback(resourceName1, resource1Devs)
   499  
   500  	resource2Devs := []pluginapi.Device{
   501  		{ID: "R2Device1", Health: pluginapi.Healthy},
   502  	}
   503  	resourceName2 := "other.domain2.org/resource2"
   504  	e2 := &endpointImpl{}
   505  	testManager.endpoints[resourceName2] = endpointInfo{e: e2, opts: nil}
   506  	testManager.genericDeviceUpdateCallback(resourceName2, resource2Devs)
   507  
   508  	allocatableDevs := testManager.GetAllocatableDevices()
   509  	as.Equal(2, len(allocatableDevs))
   510  
   511  	devInstances1, ok := allocatableDevs[resourceName1]
   512  	as.True(ok)
   513  	checkAllocatableDevicesConsistsOf(as, devInstances1, []string{"R1Device1", "R1Device2"})
   514  
   515  	devInstances2, ok := allocatableDevs[resourceName2]
   516  	as.True(ok)
   517  	checkAllocatableDevicesConsistsOf(as, devInstances2, []string{"R2Device1"})
   518  
   519  }
   520  
   521  func TestGetAllocatableDevicesHealthTransition(t *testing.T) {
   522  	socketDir, socketName, _, err := tmpSocketDir()
   523  	topologyStore := topologymanager.NewFakeManager()
   524  	require.NoError(t, err)
   525  	defer os.RemoveAll(socketDir)
   526  	testManager, err := newManagerImpl(socketName, nil, topologyStore)
   527  	as := assert.New(t)
   528  	as.NotNil(testManager)
   529  	as.Nil(err)
   530  
   531  	resource1Devs := []pluginapi.Device{
   532  		{ID: "R1Device1", Health: pluginapi.Healthy},
   533  		{ID: "R1Device2", Health: pluginapi.Healthy},
   534  		{ID: "R1Device3", Health: pluginapi.Unhealthy},
   535  	}
   536  
   537  	// Adds three devices for resource1, two healthy and one unhealthy.
   538  	// Expects allocatable devices for resource1 to be 2.
   539  	resourceName1 := "domain1.com/resource1"
   540  	e1 := &endpointImpl{}
   541  	testManager.endpoints[resourceName1] = endpointInfo{e: e1, opts: nil}
   542  
   543  	testManager.genericDeviceUpdateCallback(resourceName1, resource1Devs)
   544  
   545  	allocatableDevs := testManager.GetAllocatableDevices()
   546  	as.Equal(1, len(allocatableDevs))
   547  	devInstances, ok := allocatableDevs[resourceName1]
   548  	as.True(ok)
   549  	checkAllocatableDevicesConsistsOf(as, devInstances, []string{"R1Device1", "R1Device2"})
   550  
   551  	// Unhealthy device becomes healthy
   552  	resource1Devs = []pluginapi.Device{
   553  		{ID: "R1Device1", Health: pluginapi.Healthy},
   554  		{ID: "R1Device2", Health: pluginapi.Healthy},
   555  		{ID: "R1Device3", Health: pluginapi.Healthy},
   556  	}
   557  	testManager.genericDeviceUpdateCallback(resourceName1, resource1Devs)
   558  
   559  	allocatableDevs = testManager.GetAllocatableDevices()
   560  	as.Equal(1, len(allocatableDevs))
   561  	devInstances, ok = allocatableDevs[resourceName1]
   562  	as.True(ok)
   563  	checkAllocatableDevicesConsistsOf(as, devInstances, []string{"R1Device1", "R1Device2", "R1Device3"})
   564  }
   565  
   566  func checkAllocatableDevicesConsistsOf(as *assert.Assertions, devInstances DeviceInstances, expectedDevs []string) {
   567  	as.Equal(len(expectedDevs), len(devInstances))
   568  	for _, deviceID := range expectedDevs {
   569  		_, ok := devInstances[deviceID]
   570  		as.True(ok)
   571  	}
   572  }
   573  
   574  func constructDevices(devices []string) checkpoint.DevicesPerNUMA {
   575  	ret := checkpoint.DevicesPerNUMA{}
   576  	for _, dev := range devices {
   577  		ret[0] = append(ret[0], dev)
   578  	}
   579  	return ret
   580  }
   581  
   582  // containerAllocateResponseBuilder is a helper to build a ContainerAllocateResponse
   583  type containerAllocateResponseBuilder struct {
   584  	devices    map[string]string
   585  	mounts     map[string]string
   586  	envs       map[string]string
   587  	cdiDevices []string
   588  }
   589  
   590  // containerAllocateResponseBuilderOption defines a functional option for a containerAllocateResponseBuilder
   591  type containerAllocateResponseBuilderOption func(*containerAllocateResponseBuilder)
   592  
   593  // withDevices sets the devices for the containerAllocateResponseBuilder
   594  func withDevices(devices map[string]string) containerAllocateResponseBuilderOption {
   595  	return func(b *containerAllocateResponseBuilder) {
   596  		b.devices = devices
   597  	}
   598  }
   599  
   600  // withMounts sets the mounts for the containerAllocateResponseBuilder
   601  func withMounts(mounts map[string]string) containerAllocateResponseBuilderOption {
   602  	return func(b *containerAllocateResponseBuilder) {
   603  		b.mounts = mounts
   604  	}
   605  }
   606  
   607  // withEnvs sets the envs for the containerAllocateResponseBuilder
   608  func withEnvs(envs map[string]string) containerAllocateResponseBuilderOption {
   609  	return func(b *containerAllocateResponseBuilder) {
   610  		b.envs = envs
   611  	}
   612  }
   613  
   614  // withCDIDevices sets the cdiDevices for the containerAllocateResponseBuilder
   615  func withCDIDevices(cdiDevices ...string) containerAllocateResponseBuilderOption {
   616  	return func(b *containerAllocateResponseBuilder) {
   617  		b.cdiDevices = cdiDevices
   618  	}
   619  }
   620  
   621  // newContainerAllocateResponse creates a ContainerAllocateResponse with the given options.
   622  func newContainerAllocateResponse(opts ...containerAllocateResponseBuilderOption) *pluginapi.ContainerAllocateResponse {
   623  	b := &containerAllocateResponseBuilder{}
   624  	for _, opt := range opts {
   625  		opt(b)
   626  	}
   627  
   628  	return b.Build()
   629  }
   630  
   631  // Build uses the configured builder to create a ContainerAllocateResponse.
   632  func (b *containerAllocateResponseBuilder) Build() *pluginapi.ContainerAllocateResponse {
   633  	resp := &pluginapi.ContainerAllocateResponse{}
   634  	for k, v := range b.devices {
   635  		resp.Devices = append(resp.Devices, &pluginapi.DeviceSpec{
   636  			HostPath:      k,
   637  			ContainerPath: v,
   638  			Permissions:   "mrw",
   639  		})
   640  	}
   641  	for k, v := range b.mounts {
   642  		resp.Mounts = append(resp.Mounts, &pluginapi.Mount{
   643  			ContainerPath: k,
   644  			HostPath:      v,
   645  			ReadOnly:      true,
   646  		})
   647  	}
   648  	resp.Envs = make(map[string]string)
   649  	for k, v := range b.envs {
   650  		resp.Envs[k] = v
   651  	}
   652  
   653  	var cdiDevices []*pluginapi.CDIDevice
   654  	for _, dev := range b.cdiDevices {
   655  		cdiDevice := pluginapi.CDIDevice{
   656  			Name: dev,
   657  		}
   658  		cdiDevices = append(cdiDevices, &cdiDevice)
   659  	}
   660  	resp.CDIDevices = cdiDevices
   661  
   662  	return resp
   663  }
   664  
   665  func TestCheckpoint(t *testing.T) {
   666  	resourceName1 := "domain1.com/resource1"
   667  	resourceName2 := "domain2.com/resource2"
   668  	resourceName3 := "domain2.com/resource3"
   669  	as := assert.New(t)
   670  	tmpDir, err := os.MkdirTemp("", "checkpoint")
   671  	as.Nil(err)
   672  	defer os.RemoveAll(tmpDir)
   673  	ckm, err := checkpointmanager.NewCheckpointManager(tmpDir)
   674  	as.Nil(err)
   675  	testManager := &ManagerImpl{
   676  		endpoints:         make(map[string]endpointInfo),
   677  		healthyDevices:    make(map[string]sets.Set[string]),
   678  		unhealthyDevices:  make(map[string]sets.Set[string]),
   679  		allocatedDevices:  make(map[string]sets.Set[string]),
   680  		podDevices:        newPodDevices(),
   681  		checkpointManager: ckm,
   682  	}
   683  
   684  	testManager.podDevices.insert("pod1", "con1", resourceName1,
   685  		constructDevices([]string{"dev1", "dev2"}),
   686  		newContainerAllocateResponse(
   687  			withDevices(map[string]string{"/dev/r1dev1": "/dev/r1dev1", "/dev/r1dev2": "/dev/r1dev2"}),
   688  			withMounts(map[string]string{"/home/r1lib1": "/usr/r1lib1"}),
   689  			withCDIDevices("domain1.com/resource1=dev1", "domain1.com/resource1=dev2"),
   690  		),
   691  	)
   692  	testManager.podDevices.insert("pod1", "con1", resourceName2,
   693  		constructDevices([]string{"dev1", "dev2"}),
   694  		newContainerAllocateResponse(
   695  			withDevices(map[string]string{"/dev/r2dev1": "/dev/r2dev1", "/dev/r2dev2": "/dev/r2dev2"}),
   696  			withMounts(map[string]string{"/home/r2lib1": "/usr/r2lib1"}),
   697  			withEnvs(map[string]string{"r2devices": "dev1 dev2"}),
   698  		),
   699  	)
   700  	testManager.podDevices.insert("pod1", "con2", resourceName1,
   701  		constructDevices([]string{"dev3"}),
   702  		newContainerAllocateResponse(
   703  			withDevices(map[string]string{"/dev/r1dev3": "/dev/r1dev3"}),
   704  			withMounts(map[string]string{"/home/r1lib1": "/usr/r1lib1"}),
   705  		),
   706  	)
   707  	testManager.podDevices.insert("pod2", "con1", resourceName1,
   708  		constructDevices([]string{"dev4"}),
   709  		newContainerAllocateResponse(
   710  			withDevices(map[string]string{"/dev/r1dev4": "/dev/r1dev4"}),
   711  			withMounts(map[string]string{"/home/r1lib1": "/usr/r1lib1"}),
   712  		),
   713  	)
   714  	testManager.podDevices.insert("pod3", "con3", resourceName3,
   715  		checkpoint.DevicesPerNUMA{nodeWithoutTopology: []string{"dev5"}},
   716  		newContainerAllocateResponse(
   717  			withDevices(map[string]string{"/dev/r3dev5": "/dev/r3dev5"}),
   718  			withMounts(map[string]string{"/home/r3lib1": "/usr/r3lib1"}),
   719  		),
   720  	)
   721  
   722  	testManager.healthyDevices[resourceName1] = sets.New[string]()
   723  	testManager.healthyDevices[resourceName1].Insert("dev1")
   724  	testManager.healthyDevices[resourceName1].Insert("dev2")
   725  	testManager.healthyDevices[resourceName1].Insert("dev3")
   726  	testManager.healthyDevices[resourceName1].Insert("dev4")
   727  	testManager.healthyDevices[resourceName1].Insert("dev5")
   728  	testManager.healthyDevices[resourceName2] = sets.New[string]()
   729  	testManager.healthyDevices[resourceName2].Insert("dev1")
   730  	testManager.healthyDevices[resourceName2].Insert("dev2")
   731  	testManager.healthyDevices[resourceName3] = sets.New[string]()
   732  	testManager.healthyDevices[resourceName3].Insert("dev5")
   733  
   734  	expectedPodDevices := testManager.podDevices
   735  	expectedAllocatedDevices := testManager.podDevices.devices()
   736  	expectedAllDevices := testManager.healthyDevices
   737  
   738  	err = testManager.writeCheckpoint()
   739  
   740  	as.Nil(err)
   741  	testManager.podDevices = newPodDevices()
   742  	err = testManager.readCheckpoint()
   743  	as.Nil(err)
   744  
   745  	as.Equal(expectedPodDevices.size(), testManager.podDevices.size())
   746  	for podUID, containerDevices := range expectedPodDevices.devs {
   747  		for conName, resources := range containerDevices {
   748  			for resource := range resources {
   749  				expDevices := expectedPodDevices.containerDevices(podUID, conName, resource)
   750  				testDevices := testManager.podDevices.containerDevices(podUID, conName, resource)
   751  				as.True(reflect.DeepEqual(expDevices, testDevices))
   752  				opts1 := expectedPodDevices.deviceRunContainerOptions(podUID, conName)
   753  				opts2 := testManager.podDevices.deviceRunContainerOptions(podUID, conName)
   754  				as.Equal(len(opts1.Envs), len(opts2.Envs))
   755  				as.Equal(len(opts1.Mounts), len(opts2.Mounts))
   756  				as.Equal(len(opts1.Devices), len(opts2.Devices))
   757  			}
   758  		}
   759  	}
   760  	as.True(reflect.DeepEqual(expectedAllocatedDevices, testManager.allocatedDevices))
   761  	as.True(reflect.DeepEqual(expectedAllDevices, testManager.healthyDevices))
   762  }
   763  
   764  type activePodsStub struct {
   765  	activePods []*v1.Pod
   766  }
   767  
   768  func (a *activePodsStub) getActivePods() []*v1.Pod {
   769  	return a.activePods
   770  }
   771  
   772  func (a *activePodsStub) updateActivePods(newPods []*v1.Pod) {
   773  	a.activePods = newPods
   774  }
   775  
   776  type MockEndpoint struct {
   777  	getPreferredAllocationFunc func(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error)
   778  	allocateFunc               func(devs []string) (*pluginapi.AllocateResponse, error)
   779  	initChan                   chan []string
   780  }
   781  
   782  func (m *MockEndpoint) preStartContainer(devs []string) (*pluginapi.PreStartContainerResponse, error) {
   783  	m.initChan <- devs
   784  	return &pluginapi.PreStartContainerResponse{}, nil
   785  }
   786  
   787  func (m *MockEndpoint) getPreferredAllocation(available, mustInclude []string, size int) (*pluginapi.PreferredAllocationResponse, error) {
   788  	if m.getPreferredAllocationFunc != nil {
   789  		return m.getPreferredAllocationFunc(available, mustInclude, size)
   790  	}
   791  	return nil, nil
   792  }
   793  
   794  func (m *MockEndpoint) allocate(devs []string) (*pluginapi.AllocateResponse, error) {
   795  	if m.allocateFunc != nil {
   796  		return m.allocateFunc(devs)
   797  	}
   798  	return nil, nil
   799  }
   800  
   801  func (m *MockEndpoint) setStopTime(t time.Time) {}
   802  
   803  func (m *MockEndpoint) isStopped() bool { return false }
   804  
   805  func (m *MockEndpoint) stopGracePeriodExpired() bool { return false }
   806  
   807  func makePod(limits v1.ResourceList) *v1.Pod {
   808  	return &v1.Pod{
   809  		ObjectMeta: metav1.ObjectMeta{
   810  			UID: uuid.NewUUID(),
   811  		},
   812  		Spec: v1.PodSpec{
   813  			Containers: []v1.Container{
   814  				{
   815  					Resources: v1.ResourceRequirements{
   816  						Limits: limits,
   817  					},
   818  				},
   819  			},
   820  		},
   821  	}
   822  }
   823  
   824  func getTestManager(tmpDir string, activePods ActivePodsFunc, testRes []TestResource) (*wrappedManagerImpl, error) {
   825  	monitorCallback := func(resourceName string, devices []pluginapi.Device) {}
   826  	ckm, err := checkpointmanager.NewCheckpointManager(tmpDir)
   827  	if err != nil {
   828  		return nil, err
   829  	}
   830  	m := &ManagerImpl{
   831  		healthyDevices:        make(map[string]sets.Set[string]),
   832  		unhealthyDevices:      make(map[string]sets.Set[string]),
   833  		allocatedDevices:      make(map[string]sets.Set[string]),
   834  		endpoints:             make(map[string]endpointInfo),
   835  		podDevices:            newPodDevices(),
   836  		devicesToReuse:        make(PodReusableDevices),
   837  		topologyAffinityStore: topologymanager.NewFakeManager(),
   838  		activePods:            activePods,
   839  		sourcesReady:          &sourcesReadyStub{},
   840  		checkpointManager:     ckm,
   841  		allDevices:            NewResourceDeviceInstances(),
   842  	}
   843  	testManager := &wrappedManagerImpl{
   844  		ManagerImpl: m,
   845  		socketdir:   tmpDir,
   846  		callback:    monitorCallback,
   847  	}
   848  
   849  	for _, res := range testRes {
   850  		testManager.healthyDevices[res.resourceName] = sets.New[string](res.devs.Devices().UnsortedList()...)
   851  		if res.resourceName == "domain1.com/resource1" {
   852  			testManager.endpoints[res.resourceName] = endpointInfo{
   853  				e:    &MockEndpoint{allocateFunc: allocateStubFunc()},
   854  				opts: nil,
   855  			}
   856  		}
   857  		if res.resourceName == "domain2.com/resource2" {
   858  			testManager.endpoints[res.resourceName] = endpointInfo{
   859  				e: &MockEndpoint{
   860  					allocateFunc: func(devs []string) (*pluginapi.AllocateResponse, error) {
   861  						resp := new(pluginapi.ContainerAllocateResponse)
   862  						resp.Envs = make(map[string]string)
   863  						for _, dev := range devs {
   864  							switch dev {
   865  							case "dev3":
   866  								resp.Envs["key2"] = "val2"
   867  
   868  							case "dev4":
   869  								resp.Envs["key2"] = "val3"
   870  							}
   871  						}
   872  						resps := new(pluginapi.AllocateResponse)
   873  						resps.ContainerResponses = append(resps.ContainerResponses, resp)
   874  						return resps, nil
   875  					},
   876  				},
   877  				opts: nil,
   878  			}
   879  		}
   880  		testManager.allDevices[res.resourceName] = makeDevice(res.devs, res.topology)
   881  
   882  	}
   883  	return testManager, nil
   884  }
   885  
   886  type TestResource struct {
   887  	resourceName     string
   888  	resourceQuantity resource.Quantity
   889  	devs             checkpoint.DevicesPerNUMA
   890  	topology         bool
   891  }
   892  
   893  func TestFilterByAffinity(t *testing.T) {
   894  	as := require.New(t)
   895  	allDevices := ResourceDeviceInstances{
   896  		"res1": map[string]pluginapi.Device{
   897  			"dev1": {
   898  				ID: "dev1",
   899  				Topology: &pluginapi.TopologyInfo{
   900  					Nodes: []*pluginapi.NUMANode{
   901  						{
   902  							ID: 1,
   903  						},
   904  					},
   905  				},
   906  			},
   907  			"dev2": {
   908  				ID: "dev2",
   909  				Topology: &pluginapi.TopologyInfo{
   910  					Nodes: []*pluginapi.NUMANode{
   911  						{
   912  							ID: 1,
   913  						},
   914  						{
   915  							ID: 2,
   916  						},
   917  					},
   918  				},
   919  			},
   920  			"dev3": {
   921  				ID: "dev3",
   922  				Topology: &pluginapi.TopologyInfo{
   923  					Nodes: []*pluginapi.NUMANode{
   924  						{
   925  							ID: 2,
   926  						},
   927  					},
   928  				},
   929  			},
   930  			"dev4": {
   931  				ID: "dev4",
   932  				Topology: &pluginapi.TopologyInfo{
   933  					Nodes: []*pluginapi.NUMANode{
   934  						{
   935  							ID: 2,
   936  						},
   937  					},
   938  				},
   939  			},
   940  			"devwithouttopology": {
   941  				ID: "dev5",
   942  			},
   943  		},
   944  	}
   945  
   946  	fakeAffinity, _ := bitmask.NewBitMask(2)
   947  	fakeHint := topologymanager.TopologyHint{
   948  		NUMANodeAffinity: fakeAffinity,
   949  		Preferred:        true,
   950  	}
   951  	testManager := ManagerImpl{
   952  		topologyAffinityStore: topologymanager.NewFakeManagerWithHint(&fakeHint),
   953  		allDevices:            allDevices,
   954  	}
   955  
   956  	testCases := []struct {
   957  		available               sets.Set[string]
   958  		fromAffinityExpected    sets.Set[string]
   959  		notFromAffinityExpected sets.Set[string]
   960  		withoutTopologyExpected sets.Set[string]
   961  	}{
   962  		{
   963  			available:               sets.New[string]("dev1", "dev2"),
   964  			fromAffinityExpected:    sets.New[string]("dev2"),
   965  			notFromAffinityExpected: sets.New[string]("dev1"),
   966  			withoutTopologyExpected: sets.New[string](),
   967  		},
   968  		{
   969  			available:               sets.New[string]("dev1", "dev2", "dev3", "dev4"),
   970  			fromAffinityExpected:    sets.New[string]("dev2", "dev3", "dev4"),
   971  			notFromAffinityExpected: sets.New[string]("dev1"),
   972  			withoutTopologyExpected: sets.New[string](),
   973  		},
   974  	}
   975  
   976  	for _, testCase := range testCases {
   977  		fromAffinity, notFromAffinity, withoutTopology := testManager.filterByAffinity("", "", "res1", testCase.available)
   978  		as.Truef(fromAffinity.Equal(testCase.fromAffinityExpected), "expect devices from affinity to be %v but got %v", testCase.fromAffinityExpected, fromAffinity)
   979  		as.Truef(notFromAffinity.Equal(testCase.notFromAffinityExpected), "expect devices not from affinity to be %v but got %v", testCase.notFromAffinityExpected, notFromAffinity)
   980  		as.Truef(withoutTopology.Equal(testCase.withoutTopologyExpected), "expect devices without topology to be %v but got %v", testCase.notFromAffinityExpected, notFromAffinity)
   981  	}
   982  }
   983  
   984  func TestPodContainerDeviceAllocation(t *testing.T) {
   985  	res1 := TestResource{
   986  		resourceName:     "domain1.com/resource1",
   987  		resourceQuantity: *resource.NewQuantity(int64(2), resource.DecimalSI),
   988  		devs:             checkpoint.DevicesPerNUMA{0: []string{"dev1", "dev2"}},
   989  		topology:         true,
   990  	}
   991  	res2 := TestResource{
   992  		resourceName:     "domain2.com/resource2",
   993  		resourceQuantity: *resource.NewQuantity(int64(1), resource.DecimalSI),
   994  		devs:             checkpoint.DevicesPerNUMA{0: []string{"dev3", "dev4"}},
   995  		topology:         false,
   996  	}
   997  	testResources := make([]TestResource, 2)
   998  	testResources = append(testResources, res1)
   999  	testResources = append(testResources, res2)
  1000  	as := require.New(t)
  1001  	podsStub := activePodsStub{
  1002  		activePods: []*v1.Pod{},
  1003  	}
  1004  	tmpDir, err := os.MkdirTemp("", "checkpoint")
  1005  	as.Nil(err)
  1006  	defer os.RemoveAll(tmpDir)
  1007  	testManager, err := getTestManager(tmpDir, podsStub.getActivePods, testResources)
  1008  	as.Nil(err)
  1009  
  1010  	testPods := []*v1.Pod{
  1011  		makePod(v1.ResourceList{
  1012  			v1.ResourceName(res1.resourceName): res1.resourceQuantity,
  1013  			v1.ResourceName("cpu"):             res1.resourceQuantity,
  1014  			v1.ResourceName(res2.resourceName): res2.resourceQuantity}),
  1015  		makePod(v1.ResourceList{
  1016  			v1.ResourceName(res1.resourceName): res2.resourceQuantity}),
  1017  		makePod(v1.ResourceList{
  1018  			v1.ResourceName(res2.resourceName): res2.resourceQuantity}),
  1019  	}
  1020  	testCases := []struct {
  1021  		description               string
  1022  		testPod                   *v1.Pod
  1023  		expectedContainerOptsLen  []int
  1024  		expectedAllocatedResName1 int
  1025  		expectedAllocatedResName2 int
  1026  		expErr                    error
  1027  	}{
  1028  		{
  1029  			description:               "Successful allocation of two Res1 resources and one Res2 resource",
  1030  			testPod:                   testPods[0],
  1031  			expectedContainerOptsLen:  []int{3, 2, 2},
  1032  			expectedAllocatedResName1: 2,
  1033  			expectedAllocatedResName2: 1,
  1034  			expErr:                    nil,
  1035  		},
  1036  		{
  1037  			description:               "Requesting to create a pod without enough resources should fail",
  1038  			testPod:                   testPods[1],
  1039  			expectedContainerOptsLen:  nil,
  1040  			expectedAllocatedResName1: 2,
  1041  			expectedAllocatedResName2: 1,
  1042  			expErr:                    fmt.Errorf("requested number of devices unavailable for domain1.com/resource1. Requested: 1, Available: 0"),
  1043  		},
  1044  		{
  1045  			description:               "Successful allocation of all available Res1 resources and Res2 resources",
  1046  			testPod:                   testPods[2],
  1047  			expectedContainerOptsLen:  []int{0, 0, 1},
  1048  			expectedAllocatedResName1: 2,
  1049  			expectedAllocatedResName2: 2,
  1050  			expErr:                    nil,
  1051  		},
  1052  	}
  1053  	activePods := []*v1.Pod{}
  1054  	for _, testCase := range testCases {
  1055  		pod := testCase.testPod
  1056  		activePods = append(activePods, pod)
  1057  		podsStub.updateActivePods(activePods)
  1058  		err := testManager.Allocate(pod, &pod.Spec.Containers[0])
  1059  		if !reflect.DeepEqual(err, testCase.expErr) {
  1060  			t.Errorf("DevicePluginManager error (%v). expected error: %v but got: %v",
  1061  				testCase.description, testCase.expErr, err)
  1062  		}
  1063  		runContainerOpts, err := testManager.GetDeviceRunContainerOptions(pod, &pod.Spec.Containers[0])
  1064  		if testCase.expErr == nil {
  1065  			as.Nil(err)
  1066  		}
  1067  		if testCase.expectedContainerOptsLen == nil {
  1068  			as.Nil(runContainerOpts)
  1069  		} else {
  1070  			as.Equal(len(runContainerOpts.Devices), testCase.expectedContainerOptsLen[0])
  1071  			as.Equal(len(runContainerOpts.Mounts), testCase.expectedContainerOptsLen[1])
  1072  			as.Equal(len(runContainerOpts.Envs), testCase.expectedContainerOptsLen[2])
  1073  		}
  1074  		as.Equal(testCase.expectedAllocatedResName1, testManager.allocatedDevices[res1.resourceName].Len())
  1075  		as.Equal(testCase.expectedAllocatedResName2, testManager.allocatedDevices[res2.resourceName].Len())
  1076  	}
  1077  
  1078  }
  1079  
  1080  func TestPodContainerDeviceToAllocate(t *testing.T) {
  1081  	resourceName1 := "domain1.com/resource1"
  1082  	resourceName2 := "domain2.com/resource2"
  1083  	resourceName3 := "domain2.com/resource3"
  1084  	as := require.New(t)
  1085  	tmpDir, err := os.MkdirTemp("", "checkpoint")
  1086  	as.Nil(err)
  1087  	defer os.RemoveAll(tmpDir)
  1088  
  1089  	testManager := &ManagerImpl{
  1090  		endpoints:        make(map[string]endpointInfo),
  1091  		healthyDevices:   make(map[string]sets.Set[string]),
  1092  		unhealthyDevices: make(map[string]sets.Set[string]),
  1093  		allocatedDevices: make(map[string]sets.Set[string]),
  1094  		podDevices:       newPodDevices(),
  1095  		activePods:       func() []*v1.Pod { return []*v1.Pod{} },
  1096  		sourcesReady:     &sourcesReadyStub{},
  1097  	}
  1098  
  1099  	testManager.podDevices.insert("pod1", "con1", resourceName1,
  1100  		constructDevices([]string{"dev1", "dev2"}),
  1101  		newContainerAllocateResponse(
  1102  			withDevices(map[string]string{"/dev/r2dev1": "/dev/r2dev1", "/dev/r2dev2": "/dev/r2dev2"}),
  1103  			withMounts(map[string]string{"/home/r2lib1": "/usr/r2lib1"}),
  1104  			withEnvs(map[string]string{"r2devices": "dev1 dev2"}),
  1105  		),
  1106  	)
  1107  	testManager.podDevices.insert("pod2", "con2", resourceName2,
  1108  		checkpoint.DevicesPerNUMA{nodeWithoutTopology: []string{"dev5"}},
  1109  		newContainerAllocateResponse(
  1110  			withDevices(map[string]string{"/dev/r1dev5": "/dev/r1dev5"}),
  1111  			withMounts(map[string]string{"/home/r1lib1": "/usr/r1lib1"}),
  1112  		),
  1113  	)
  1114  	testManager.podDevices.insert("pod3", "con3", resourceName3,
  1115  		checkpoint.DevicesPerNUMA{nodeWithoutTopology: []string{"dev5"}},
  1116  		newContainerAllocateResponse(
  1117  			withDevices(map[string]string{"/dev/r1dev5": "/dev/r1dev5"}),
  1118  			withMounts(map[string]string{"/home/r1lib1": "/usr/r1lib1"}),
  1119  		),
  1120  	)
  1121  
  1122  	// no healthy devices for resourceName1 and devices corresponding to
  1123  	// resource2 are intentionally omitted to simulate that the resource
  1124  	// hasn't been registered.
  1125  	testManager.healthyDevices[resourceName1] = sets.New[string]()
  1126  	testManager.healthyDevices[resourceName3] = sets.New[string]()
  1127  	// dev5 is no longer in the list of healthy devices
  1128  	testManager.healthyDevices[resourceName3].Insert("dev7")
  1129  	testManager.healthyDevices[resourceName3].Insert("dev8")
  1130  
  1131  	testCases := []struct {
  1132  		description              string
  1133  		podUID                   string
  1134  		contName                 string
  1135  		resource                 string
  1136  		required                 int
  1137  		reusableDevices          sets.Set[string]
  1138  		expectedAllocatedDevices sets.Set[string]
  1139  		expErr                   error
  1140  	}{
  1141  		{
  1142  			description:              "Admission error in case no healthy devices to allocate present",
  1143  			podUID:                   "pod1",
  1144  			contName:                 "con1",
  1145  			resource:                 resourceName1,
  1146  			required:                 2,
  1147  			reusableDevices:          sets.New[string](),
  1148  			expectedAllocatedDevices: nil,
  1149  			expErr:                   fmt.Errorf("no healthy devices present; cannot allocate unhealthy devices %s", resourceName1),
  1150  		},
  1151  		{
  1152  			description:              "Admission error in case resource is not registered",
  1153  			podUID:                   "pod2",
  1154  			contName:                 "con2",
  1155  			resource:                 resourceName2,
  1156  			required:                 1,
  1157  			reusableDevices:          sets.New[string](),
  1158  			expectedAllocatedDevices: nil,
  1159  			expErr:                   fmt.Errorf("cannot allocate unregistered device %s", resourceName2),
  1160  		},
  1161  		{
  1162  			description:              "Admission error in case resource not devices previously allocated no longer healthy",
  1163  			podUID:                   "pod3",
  1164  			contName:                 "con3",
  1165  			resource:                 resourceName3,
  1166  			required:                 1,
  1167  			reusableDevices:          sets.New[string](),
  1168  			expectedAllocatedDevices: nil,
  1169  			expErr:                   fmt.Errorf("previously allocated devices are no longer healthy; cannot allocate unhealthy devices %s", resourceName3),
  1170  		},
  1171  	}
  1172  
  1173  	for _, testCase := range testCases {
  1174  		allocDevices, err := testManager.devicesToAllocate(testCase.podUID, testCase.contName, testCase.resource, testCase.required, testCase.reusableDevices)
  1175  		if !reflect.DeepEqual(err, testCase.expErr) {
  1176  			t.Errorf("devicePluginManager error (%v). expected error: %v but got: %v",
  1177  				testCase.description, testCase.expErr, err)
  1178  		}
  1179  		if !reflect.DeepEqual(allocDevices, testCase.expectedAllocatedDevices) {
  1180  			t.Errorf("devicePluginManager error (%v). expected error: %v but got: %v",
  1181  				testCase.description, testCase.expectedAllocatedDevices, allocDevices)
  1182  		}
  1183  	}
  1184  
  1185  }
  1186  
  1187  func TestGetDeviceRunContainerOptions(t *testing.T) {
  1188  	res1 := TestResource{
  1189  		resourceName:     "domain1.com/resource1",
  1190  		resourceQuantity: *resource.NewQuantity(int64(2), resource.DecimalSI),
  1191  		devs:             checkpoint.DevicesPerNUMA{0: []string{"dev1", "dev2"}},
  1192  		topology:         true,
  1193  	}
  1194  	res2 := TestResource{
  1195  		resourceName:     "domain2.com/resource2",
  1196  		resourceQuantity: *resource.NewQuantity(int64(1), resource.DecimalSI),
  1197  		devs:             checkpoint.DevicesPerNUMA{0: []string{"dev3", "dev4"}},
  1198  		topology:         false,
  1199  	}
  1200  
  1201  	testResources := make([]TestResource, 2)
  1202  	testResources = append(testResources, res1)
  1203  	testResources = append(testResources, res2)
  1204  
  1205  	podsStub := activePodsStub{
  1206  		activePods: []*v1.Pod{},
  1207  	}
  1208  	as := require.New(t)
  1209  
  1210  	tmpDir, err := os.MkdirTemp("", "checkpoint")
  1211  	as.Nil(err)
  1212  	defer os.RemoveAll(tmpDir)
  1213  
  1214  	testManager, err := getTestManager(tmpDir, podsStub.getActivePods, testResources)
  1215  	as.Nil(err)
  1216  
  1217  	pod1 := makePod(v1.ResourceList{
  1218  		v1.ResourceName(res1.resourceName): res1.resourceQuantity,
  1219  		v1.ResourceName(res2.resourceName): res2.resourceQuantity,
  1220  	})
  1221  	pod2 := makePod(v1.ResourceList{
  1222  		v1.ResourceName(res2.resourceName): res2.resourceQuantity,
  1223  	})
  1224  
  1225  	activePods := []*v1.Pod{pod1, pod2}
  1226  	podsStub.updateActivePods(activePods)
  1227  
  1228  	err = testManager.Allocate(pod1, &pod1.Spec.Containers[0])
  1229  	as.Nil(err)
  1230  	err = testManager.Allocate(pod2, &pod2.Spec.Containers[0])
  1231  	as.Nil(err)
  1232  
  1233  	// when pod is in activePods, GetDeviceRunContainerOptions should return
  1234  	runContainerOpts, err := testManager.GetDeviceRunContainerOptions(pod1, &pod1.Spec.Containers[0])
  1235  	as.Nil(err)
  1236  	as.Equal(len(runContainerOpts.Devices), 3)
  1237  	as.Equal(len(runContainerOpts.Mounts), 2)
  1238  	as.Equal(len(runContainerOpts.Envs), 2)
  1239  
  1240  	activePods = []*v1.Pod{pod2}
  1241  	podsStub.updateActivePods(activePods)
  1242  	testManager.UpdateAllocatedDevices()
  1243  
  1244  	// when pod is removed from activePods,G etDeviceRunContainerOptions should return error
  1245  	runContainerOpts, err = testManager.GetDeviceRunContainerOptions(pod1, &pod1.Spec.Containers[0])
  1246  	as.Nil(err)
  1247  	as.Nil(runContainerOpts)
  1248  }
  1249  
  1250  func TestInitContainerDeviceAllocation(t *testing.T) {
  1251  	// Requesting to create a pod that requests resourceName1 in init containers and normal containers
  1252  	// should succeed with devices allocated to init containers reallocated to normal containers.
  1253  	res1 := TestResource{
  1254  		resourceName:     "domain1.com/resource1",
  1255  		resourceQuantity: *resource.NewQuantity(int64(2), resource.DecimalSI),
  1256  		devs:             checkpoint.DevicesPerNUMA{0: []string{"dev1", "dev2"}},
  1257  		topology:         false,
  1258  	}
  1259  	res2 := TestResource{
  1260  		resourceName:     "domain2.com/resource2",
  1261  		resourceQuantity: *resource.NewQuantity(int64(1), resource.DecimalSI),
  1262  		devs:             checkpoint.DevicesPerNUMA{0: []string{"dev3", "dev4"}},
  1263  		topology:         true,
  1264  	}
  1265  	testResources := make([]TestResource, 2)
  1266  	testResources = append(testResources, res1)
  1267  	testResources = append(testResources, res2)
  1268  	as := require.New(t)
  1269  	podsStub := activePodsStub{
  1270  		activePods: []*v1.Pod{},
  1271  	}
  1272  	tmpDir, err := os.MkdirTemp("", "checkpoint")
  1273  	as.Nil(err)
  1274  	defer os.RemoveAll(tmpDir)
  1275  
  1276  	testManager, err := getTestManager(tmpDir, podsStub.getActivePods, testResources)
  1277  	as.Nil(err)
  1278  
  1279  	podWithPluginResourcesInInitContainers := &v1.Pod{
  1280  		ObjectMeta: metav1.ObjectMeta{
  1281  			UID: uuid.NewUUID(),
  1282  		},
  1283  		Spec: v1.PodSpec{
  1284  			InitContainers: []v1.Container{
  1285  				{
  1286  					Name: string(uuid.NewUUID()),
  1287  					Resources: v1.ResourceRequirements{
  1288  						Limits: v1.ResourceList{
  1289  							v1.ResourceName(res1.resourceName): res2.resourceQuantity,
  1290  						},
  1291  					},
  1292  				},
  1293  				{
  1294  					Name: string(uuid.NewUUID()),
  1295  					Resources: v1.ResourceRequirements{
  1296  						Limits: v1.ResourceList{
  1297  							v1.ResourceName(res1.resourceName): res1.resourceQuantity,
  1298  						},
  1299  					},
  1300  				},
  1301  			},
  1302  			Containers: []v1.Container{
  1303  				{
  1304  					Name: string(uuid.NewUUID()),
  1305  					Resources: v1.ResourceRequirements{
  1306  						Limits: v1.ResourceList{
  1307  							v1.ResourceName(res1.resourceName): res2.resourceQuantity,
  1308  							v1.ResourceName(res2.resourceName): res2.resourceQuantity,
  1309  						},
  1310  					},
  1311  				},
  1312  				{
  1313  					Name: string(uuid.NewUUID()),
  1314  					Resources: v1.ResourceRequirements{
  1315  						Limits: v1.ResourceList{
  1316  							v1.ResourceName(res1.resourceName): res2.resourceQuantity,
  1317  							v1.ResourceName(res2.resourceName): res2.resourceQuantity,
  1318  						},
  1319  					},
  1320  				},
  1321  			},
  1322  		},
  1323  	}
  1324  	podsStub.updateActivePods([]*v1.Pod{podWithPluginResourcesInInitContainers})
  1325  	for _, container := range podWithPluginResourcesInInitContainers.Spec.InitContainers {
  1326  		err = testManager.Allocate(podWithPluginResourcesInInitContainers, &container)
  1327  	}
  1328  	for _, container := range podWithPluginResourcesInInitContainers.Spec.Containers {
  1329  		err = testManager.Allocate(podWithPluginResourcesInInitContainers, &container)
  1330  	}
  1331  	as.Nil(err)
  1332  	podUID := string(podWithPluginResourcesInInitContainers.UID)
  1333  	initCont1 := podWithPluginResourcesInInitContainers.Spec.InitContainers[0].Name
  1334  	initCont2 := podWithPluginResourcesInInitContainers.Spec.InitContainers[1].Name
  1335  	normalCont1 := podWithPluginResourcesInInitContainers.Spec.Containers[0].Name
  1336  	normalCont2 := podWithPluginResourcesInInitContainers.Spec.Containers[1].Name
  1337  	initCont1Devices := testManager.podDevices.containerDevices(podUID, initCont1, res1.resourceName)
  1338  	initCont2Devices := testManager.podDevices.containerDevices(podUID, initCont2, res1.resourceName)
  1339  	normalCont1Devices := testManager.podDevices.containerDevices(podUID, normalCont1, res1.resourceName)
  1340  	normalCont2Devices := testManager.podDevices.containerDevices(podUID, normalCont2, res1.resourceName)
  1341  	as.Equal(1, initCont1Devices.Len())
  1342  	as.Equal(2, initCont2Devices.Len())
  1343  	as.Equal(1, normalCont1Devices.Len())
  1344  	as.Equal(1, normalCont2Devices.Len())
  1345  	as.True(initCont2Devices.IsSuperset(initCont1Devices))
  1346  	as.True(initCont2Devices.IsSuperset(normalCont1Devices))
  1347  	as.True(initCont2Devices.IsSuperset(normalCont2Devices))
  1348  	as.Equal(0, normalCont1Devices.Intersection(normalCont2Devices).Len())
  1349  }
  1350  
  1351  func TestRestartableInitContainerDeviceAllocation(t *testing.T) {
  1352  	// Requesting to create a pod that requests resourceName1 in restartable
  1353  	// init containers and normal containers should succeed with devices
  1354  	// allocated to init containers not reallocated to normal containers.
  1355  	oneDevice := resource.NewQuantity(int64(1), resource.DecimalSI)
  1356  	twoDevice := resource.NewQuantity(int64(2), resource.DecimalSI)
  1357  	threeDevice := resource.NewQuantity(int64(3), resource.DecimalSI)
  1358  	res1 := TestResource{
  1359  		resourceName:     "domain1.com/resource1",
  1360  		resourceQuantity: *resource.NewQuantity(int64(6), resource.DecimalSI),
  1361  		devs: checkpoint.DevicesPerNUMA{
  1362  			0: []string{"dev1", "dev2", "dev3", "dev4", "dev5", "dev6"},
  1363  		},
  1364  		topology: false,
  1365  	}
  1366  	testResources := []TestResource{
  1367  		res1,
  1368  	}
  1369  	as := require.New(t)
  1370  	podsStub := activePodsStub{
  1371  		activePods: []*v1.Pod{},
  1372  	}
  1373  	tmpDir, err := os.MkdirTemp("", "checkpoint")
  1374  	as.Nil(err)
  1375  	defer os.RemoveAll(tmpDir)
  1376  
  1377  	testManager, err := getTestManager(tmpDir, podsStub.getActivePods, testResources)
  1378  	as.Nil(err)
  1379  
  1380  	containerRestartPolicyAlways := v1.ContainerRestartPolicyAlways
  1381  	podWithPluginResourcesInRestartableInitContainers := &v1.Pod{
  1382  		ObjectMeta: metav1.ObjectMeta{
  1383  			UID: uuid.NewUUID(),
  1384  		},
  1385  		Spec: v1.PodSpec{
  1386  			InitContainers: []v1.Container{
  1387  				{
  1388  					Name: string(uuid.NewUUID()),
  1389  					Resources: v1.ResourceRequirements{
  1390  						Limits: v1.ResourceList{
  1391  							v1.ResourceName(res1.resourceName): *threeDevice,
  1392  						},
  1393  					},
  1394  				},
  1395  				{
  1396  					Name: string(uuid.NewUUID()),
  1397  					Resources: v1.ResourceRequirements{
  1398  						Limits: v1.ResourceList{
  1399  							v1.ResourceName(res1.resourceName): *oneDevice,
  1400  						},
  1401  					},
  1402  					RestartPolicy: &containerRestartPolicyAlways,
  1403  				},
  1404  				{
  1405  					Name: string(uuid.NewUUID()),
  1406  					Resources: v1.ResourceRequirements{
  1407  						Limits: v1.ResourceList{
  1408  							v1.ResourceName(res1.resourceName): *twoDevice,
  1409  						},
  1410  					},
  1411  					RestartPolicy: &containerRestartPolicyAlways,
  1412  				},
  1413  			},
  1414  			Containers: []v1.Container{
  1415  				{
  1416  					Name: string(uuid.NewUUID()),
  1417  					Resources: v1.ResourceRequirements{
  1418  						Limits: v1.ResourceList{
  1419  							v1.ResourceName(res1.resourceName): *oneDevice,
  1420  						},
  1421  					},
  1422  				},
  1423  				{
  1424  					Name: string(uuid.NewUUID()),
  1425  					Resources: v1.ResourceRequirements{
  1426  						Limits: v1.ResourceList{
  1427  							v1.ResourceName(res1.resourceName): *twoDevice,
  1428  						},
  1429  					},
  1430  				},
  1431  			},
  1432  		},
  1433  	}
  1434  	podsStub.updateActivePods([]*v1.Pod{podWithPluginResourcesInRestartableInitContainers})
  1435  	for _, container := range podWithPluginResourcesInRestartableInitContainers.Spec.InitContainers {
  1436  		err = testManager.Allocate(podWithPluginResourcesInRestartableInitContainers, &container)
  1437  	}
  1438  	for _, container := range podWithPluginResourcesInRestartableInitContainers.Spec.Containers {
  1439  		err = testManager.Allocate(podWithPluginResourcesInRestartableInitContainers, &container)
  1440  	}
  1441  	as.Nil(err)
  1442  	podUID := string(podWithPluginResourcesInRestartableInitContainers.UID)
  1443  	regularInitCont1 := podWithPluginResourcesInRestartableInitContainers.Spec.InitContainers[0].Name
  1444  	restartableInitCont2 := podWithPluginResourcesInRestartableInitContainers.Spec.InitContainers[1].Name
  1445  	restartableInitCont3 := podWithPluginResourcesInRestartableInitContainers.Spec.InitContainers[2].Name
  1446  	normalCont1 := podWithPluginResourcesInRestartableInitContainers.Spec.Containers[0].Name
  1447  	normalCont2 := podWithPluginResourcesInRestartableInitContainers.Spec.Containers[1].Name
  1448  	regularInitCont1Devices := testManager.podDevices.containerDevices(podUID, regularInitCont1, res1.resourceName)
  1449  	restartableInitCont2Devices := testManager.podDevices.containerDevices(podUID, restartableInitCont2, res1.resourceName)
  1450  	restartableInitCont3Devices := testManager.podDevices.containerDevices(podUID, restartableInitCont3, res1.resourceName)
  1451  	normalCont1Devices := testManager.podDevices.containerDevices(podUID, normalCont1, res1.resourceName)
  1452  	normalCont2Devices := testManager.podDevices.containerDevices(podUID, normalCont2, res1.resourceName)
  1453  	as.Equal(3, regularInitCont1Devices.Len())
  1454  	as.Equal(1, restartableInitCont2Devices.Len())
  1455  	as.Equal(2, restartableInitCont3Devices.Len())
  1456  	as.Equal(1, normalCont1Devices.Len())
  1457  	as.Equal(2, normalCont2Devices.Len())
  1458  	as.True(regularInitCont1Devices.IsSuperset(restartableInitCont2Devices))
  1459  	as.True(regularInitCont1Devices.IsSuperset(restartableInitCont3Devices))
  1460  	// regularInitCont1Devices are sharable with other containers
  1461  
  1462  	dedicatedContainerDevices := []sets.Set[string]{
  1463  		restartableInitCont2Devices,
  1464  		restartableInitCont3Devices,
  1465  		normalCont1Devices,
  1466  		normalCont2Devices,
  1467  	}
  1468  
  1469  	for i := 0; i < len(dedicatedContainerDevices)-1; i++ {
  1470  		for j := i + 1; j < len(dedicatedContainerDevices); j++ {
  1471  			t.Logf("containerDevices[%d] = %v", i, dedicatedContainerDevices[i])
  1472  			t.Logf("containerDevices[%d] = %v", j, dedicatedContainerDevices[j])
  1473  			as.Empty(dedicatedContainerDevices[i].Intersection(dedicatedContainerDevices[j]))
  1474  		}
  1475  	}
  1476  }
  1477  
  1478  func TestUpdatePluginResources(t *testing.T) {
  1479  	pod := &v1.Pod{}
  1480  	pod.UID = types.UID("testPod")
  1481  
  1482  	resourceName1 := "domain1.com/resource1"
  1483  	devID1 := "dev1"
  1484  
  1485  	resourceName2 := "domain2.com/resource2"
  1486  	devID2 := "dev2"
  1487  
  1488  	as := assert.New(t)
  1489  	monitorCallback := func(resourceName string, devices []pluginapi.Device) {}
  1490  	tmpDir, err := os.MkdirTemp("", "checkpoint")
  1491  	as.Nil(err)
  1492  	defer os.RemoveAll(tmpDir)
  1493  
  1494  	ckm, err := checkpointmanager.NewCheckpointManager(tmpDir)
  1495  	as.Nil(err)
  1496  	m := &ManagerImpl{
  1497  		allocatedDevices:  make(map[string]sets.Set[string]),
  1498  		healthyDevices:    make(map[string]sets.Set[string]),
  1499  		podDevices:        newPodDevices(),
  1500  		checkpointManager: ckm,
  1501  	}
  1502  	testManager := wrappedManagerImpl{
  1503  		ManagerImpl: m,
  1504  		callback:    monitorCallback,
  1505  	}
  1506  	testManager.podDevices.devs[string(pod.UID)] = make(containerDevices)
  1507  
  1508  	// require one of resource1 and one of resource2
  1509  	testManager.allocatedDevices[resourceName1] = sets.New[string]()
  1510  	testManager.allocatedDevices[resourceName1].Insert(devID1)
  1511  	testManager.allocatedDevices[resourceName2] = sets.New[string]()
  1512  	testManager.allocatedDevices[resourceName2].Insert(devID2)
  1513  
  1514  	cachedNode := &v1.Node{
  1515  		Status: v1.NodeStatus{
  1516  			Allocatable: v1.ResourceList{
  1517  				// has no resource1 and two of resource2
  1518  				v1.ResourceName(resourceName2): *resource.NewQuantity(int64(2), resource.DecimalSI),
  1519  			},
  1520  		},
  1521  	}
  1522  	nodeInfo := &schedulerframework.NodeInfo{}
  1523  	nodeInfo.SetNode(cachedNode)
  1524  
  1525  	testManager.UpdatePluginResources(nodeInfo, &lifecycle.PodAdmitAttributes{Pod: pod})
  1526  
  1527  	allocatableScalarResources := nodeInfo.Allocatable.ScalarResources
  1528  	// allocatable in nodeInfo is less than needed, should update
  1529  	as.Equal(1, int(allocatableScalarResources[v1.ResourceName(resourceName1)]))
  1530  	// allocatable in nodeInfo is more than needed, should skip updating
  1531  	as.Equal(2, int(allocatableScalarResources[v1.ResourceName(resourceName2)]))
  1532  }
  1533  
  1534  func TestDevicePreStartContainer(t *testing.T) {
  1535  	// Ensures that if device manager is indicated to invoke `PreStartContainer` RPC
  1536  	// by device plugin, then device manager invokes PreStartContainer at endpoint interface.
  1537  	// Also verifies that final allocation of mounts, envs etc is same as expected.
  1538  	res1 := TestResource{
  1539  		resourceName:     "domain1.com/resource1",
  1540  		resourceQuantity: *resource.NewQuantity(int64(2), resource.DecimalSI),
  1541  		devs:             checkpoint.DevicesPerNUMA{0: []string{"dev1", "dev2"}},
  1542  		topology:         false,
  1543  	}
  1544  	as := require.New(t)
  1545  	podsStub := activePodsStub{
  1546  		activePods: []*v1.Pod{},
  1547  	}
  1548  	tmpDir, err := os.MkdirTemp("", "checkpoint")
  1549  	as.Nil(err)
  1550  	defer os.RemoveAll(tmpDir)
  1551  
  1552  	testManager, err := getTestManager(tmpDir, podsStub.getActivePods, []TestResource{res1})
  1553  	as.Nil(err)
  1554  
  1555  	ch := make(chan []string, 1)
  1556  	testManager.endpoints[res1.resourceName] = endpointInfo{
  1557  		e: &MockEndpoint{
  1558  			initChan:     ch,
  1559  			allocateFunc: allocateStubFunc(),
  1560  		},
  1561  		opts: &pluginapi.DevicePluginOptions{PreStartRequired: true},
  1562  	}
  1563  	pod := makePod(v1.ResourceList{
  1564  		v1.ResourceName(res1.resourceName): res1.resourceQuantity})
  1565  	activePods := []*v1.Pod{}
  1566  	activePods = append(activePods, pod)
  1567  	podsStub.updateActivePods(activePods)
  1568  	err = testManager.Allocate(pod, &pod.Spec.Containers[0])
  1569  	as.Nil(err)
  1570  	runContainerOpts, err := testManager.GetDeviceRunContainerOptions(pod, &pod.Spec.Containers[0])
  1571  	as.Nil(err)
  1572  	var initializedDevs []string
  1573  	select {
  1574  	case <-time.After(time.Second):
  1575  		t.Fatalf("Timed out while waiting on channel for response from PreStartContainer RPC stub")
  1576  	case initializedDevs = <-ch:
  1577  		break
  1578  	}
  1579  
  1580  	as.Contains(initializedDevs, "dev1")
  1581  	as.Contains(initializedDevs, "dev2")
  1582  	as.Equal(len(initializedDevs), res1.devs.Devices().Len())
  1583  
  1584  	expectedResps, err := allocateStubFunc()([]string{"dev1", "dev2"})
  1585  	as.Nil(err)
  1586  	as.Equal(1, len(expectedResps.ContainerResponses))
  1587  	expectedResp := expectedResps.ContainerResponses[0]
  1588  	as.Equal(len(runContainerOpts.Devices), len(expectedResp.Devices))
  1589  	as.Equal(len(runContainerOpts.Mounts), len(expectedResp.Mounts))
  1590  	as.Equal(len(runContainerOpts.Envs), len(expectedResp.Envs))
  1591  
  1592  	pod2 := makePod(v1.ResourceList{
  1593  		v1.ResourceName(res1.resourceName): *resource.NewQuantity(int64(0), resource.DecimalSI)})
  1594  	activePods = append(activePods, pod2)
  1595  	podsStub.updateActivePods(activePods)
  1596  	err = testManager.Allocate(pod2, &pod2.Spec.Containers[0])
  1597  	as.Nil(err)
  1598  	_, err = testManager.GetDeviceRunContainerOptions(pod2, &pod2.Spec.Containers[0])
  1599  	as.Nil(err)
  1600  	select {
  1601  	case <-time.After(time.Millisecond):
  1602  		t.Log("When pod resourceQuantity is 0,  PreStartContainer RPC stub will be skipped")
  1603  	case <-ch:
  1604  		break
  1605  	}
  1606  }
  1607  
  1608  func TestResetExtendedResource(t *testing.T) {
  1609  	as := assert.New(t)
  1610  	tmpDir, err := os.MkdirTemp("", "checkpoint")
  1611  	as.Nil(err)
  1612  	defer os.RemoveAll(tmpDir)
  1613  	ckm, err := checkpointmanager.NewCheckpointManager(tmpDir)
  1614  	as.Nil(err)
  1615  	testManager := &ManagerImpl{
  1616  		endpoints:         make(map[string]endpointInfo),
  1617  		healthyDevices:    make(map[string]sets.Set[string]),
  1618  		unhealthyDevices:  make(map[string]sets.Set[string]),
  1619  		allocatedDevices:  make(map[string]sets.Set[string]),
  1620  		podDevices:        newPodDevices(),
  1621  		checkpointManager: ckm,
  1622  	}
  1623  
  1624  	extendedResourceName := "domain.com/resource"
  1625  	testManager.podDevices.insert("pod", "con", extendedResourceName,
  1626  		constructDevices([]string{"dev1"}),
  1627  		newContainerAllocateResponse(
  1628  			withDevices(map[string]string{"/dev/dev1": "/dev/dev1"}),
  1629  			withMounts(map[string]string{"/home/lib1": "/usr/lib1"}),
  1630  		),
  1631  	)
  1632  
  1633  	testManager.healthyDevices[extendedResourceName] = sets.New[string]()
  1634  	testManager.healthyDevices[extendedResourceName].Insert("dev1")
  1635  	// checkpoint is present, indicating node hasn't been recreated
  1636  	err = testManager.writeCheckpoint()
  1637  	as.Nil(err)
  1638  
  1639  	as.False(testManager.ShouldResetExtendedResourceCapacity())
  1640  
  1641  	// checkpoint is absent, representing node recreation
  1642  	ckpts, err := ckm.ListCheckpoints()
  1643  	as.Nil(err)
  1644  	for _, ckpt := range ckpts {
  1645  		err = ckm.RemoveCheckpoint(ckpt)
  1646  		as.Nil(err)
  1647  	}
  1648  	as.True(testManager.ShouldResetExtendedResourceCapacity())
  1649  }
  1650  
  1651  func allocateStubFunc() func(devs []string) (*pluginapi.AllocateResponse, error) {
  1652  	return func(devs []string) (*pluginapi.AllocateResponse, error) {
  1653  		resp := new(pluginapi.ContainerAllocateResponse)
  1654  		resp.Envs = make(map[string]string)
  1655  		for _, dev := range devs {
  1656  			switch dev {
  1657  			case "dev1":
  1658  				resp.Devices = append(resp.Devices, &pluginapi.DeviceSpec{
  1659  					ContainerPath: "/dev/aaa",
  1660  					HostPath:      "/dev/aaa",
  1661  					Permissions:   "mrw",
  1662  				})
  1663  
  1664  				resp.Devices = append(resp.Devices, &pluginapi.DeviceSpec{
  1665  					ContainerPath: "/dev/bbb",
  1666  					HostPath:      "/dev/bbb",
  1667  					Permissions:   "mrw",
  1668  				})
  1669  
  1670  				resp.Mounts = append(resp.Mounts, &pluginapi.Mount{
  1671  					ContainerPath: "/container_dir1/file1",
  1672  					HostPath:      "host_dir1/file1",
  1673  					ReadOnly:      true,
  1674  				})
  1675  
  1676  			case "dev2":
  1677  				resp.Devices = append(resp.Devices, &pluginapi.DeviceSpec{
  1678  					ContainerPath: "/dev/ccc",
  1679  					HostPath:      "/dev/ccc",
  1680  					Permissions:   "mrw",
  1681  				})
  1682  
  1683  				resp.Mounts = append(resp.Mounts, &pluginapi.Mount{
  1684  					ContainerPath: "/container_dir1/file2",
  1685  					HostPath:      "host_dir1/file2",
  1686  					ReadOnly:      true,
  1687  				})
  1688  
  1689  				resp.Envs["key1"] = "val1"
  1690  			}
  1691  		}
  1692  		resps := new(pluginapi.AllocateResponse)
  1693  		resps.ContainerResponses = append(resps.ContainerResponses, resp)
  1694  		return resps, nil
  1695  	}
  1696  }
  1697  
  1698  func makeDevice(devOnNUMA checkpoint.DevicesPerNUMA, topology bool) map[string]pluginapi.Device {
  1699  	res := make(map[string]pluginapi.Device)
  1700  	var topologyInfo *pluginapi.TopologyInfo
  1701  	for node, devs := range devOnNUMA {
  1702  		if topology {
  1703  			topologyInfo = &pluginapi.TopologyInfo{Nodes: []*pluginapi.NUMANode{{ID: node}}}
  1704  		} else {
  1705  			topologyInfo = nil
  1706  		}
  1707  		for idx := range devs {
  1708  			res[devs[idx]] = pluginapi.Device{ID: devs[idx], Topology: topologyInfo}
  1709  		}
  1710  	}
  1711  	return res
  1712  }
  1713  
  1714  const deviceManagerCheckpointFilename = "kubelet_internal_checkpoint"
  1715  
  1716  var oldCheckpoint string = `{"Data":{"PodDeviceEntries":[{"PodUID":"13ac2284-0d19-44b7-b94f-055b032dba9b","ContainerName":"centos","ResourceName":"example.com/deviceA","DeviceIDs":["DevA3"],"AllocResp":"CiIKHUVYQU1QTEVDT01ERVZJQ0VBX0RFVkEzX1RUWTEwEgEwGhwKCi9kZXYvdHR5MTASCi9kZXYvdHR5MTAaAnJ3"},{"PodUID":"86b9a017-c9ca-4069-815f-46ca3e53c1e4","ContainerName":"centos","ResourceName":"example.com/deviceA","DeviceIDs":["DevA4"],"AllocResp":"CiIKHUVYQU1QTEVDT01ERVZJQ0VBX0RFVkE0X1RUWTExEgEwGhwKCi9kZXYvdHR5MTESCi9kZXYvdHR5MTEaAnJ3"}],"RegisteredDevices":{"example.com/deviceA":["DevA1","DevA2","DevA3","DevA4"]}},"Checksum":405612085}`
  1717  
  1718  func TestReadPreNUMACheckpoint(t *testing.T) {
  1719  	socketDir, socketName, _, err := tmpSocketDir()
  1720  	require.NoError(t, err)
  1721  	defer os.RemoveAll(socketDir)
  1722  
  1723  	err = os.WriteFile(filepath.Join(socketDir, deviceManagerCheckpointFilename), []byte(oldCheckpoint), 0644)
  1724  	require.NoError(t, err)
  1725  
  1726  	topologyStore := topologymanager.NewFakeManager()
  1727  	nodes := []cadvisorapi.Node{{Id: 0}}
  1728  	m, err := newManagerImpl(socketName, nodes, topologyStore)
  1729  	require.NoError(t, err)
  1730  
  1731  	// TODO: we should not calling private methods, but among the existing tests we do anyway
  1732  	err = m.readCheckpoint()
  1733  	require.NoError(t, err)
  1734  }
  1735  
  1736  func TestGetTopologyHintsWithUpdates(t *testing.T) {
  1737  	socketDir, socketName, _, err := tmpSocketDir()
  1738  	defer os.RemoveAll(socketDir)
  1739  	require.NoError(t, err)
  1740  
  1741  	devs := []pluginapi.Device{}
  1742  	for i := 0; i < 1000; i++ {
  1743  		devs = append(devs, pluginapi.Device{
  1744  			ID:     fmt.Sprintf("dev-%d", i),
  1745  			Health: pluginapi.Healthy,
  1746  			Topology: &pluginapi.TopologyInfo{
  1747  				Nodes: []*pluginapi.NUMANode{
  1748  					{ID: 0},
  1749  				},
  1750  			}})
  1751  	}
  1752  	testPod := makePod(v1.ResourceList{
  1753  		testResourceName: *resource.NewQuantity(int64(1), resource.DecimalSI),
  1754  	})
  1755  	topology := []cadvisorapi.Node{
  1756  		{Id: 0},
  1757  	}
  1758  	testCases := []struct {
  1759  		description string
  1760  		count       int
  1761  		devices     []pluginapi.Device
  1762  		testfunc    func(manager *wrappedManagerImpl)
  1763  	}{
  1764  		{
  1765  			description: "GetTopologyHints data race when update device",
  1766  			count:       10,
  1767  			devices:     devs,
  1768  			testfunc: func(manager *wrappedManagerImpl) {
  1769  				manager.GetTopologyHints(testPod, &testPod.Spec.Containers[0])
  1770  			},
  1771  		},
  1772  		{
  1773  			description: "GetPodTopologyHints data race when update device",
  1774  			count:       10,
  1775  			devices:     devs,
  1776  			testfunc: func(manager *wrappedManagerImpl) {
  1777  				manager.GetPodTopologyHints(testPod)
  1778  			},
  1779  		},
  1780  	}
  1781  
  1782  	for _, test := range testCases {
  1783  		t.Run(test.description, func(t *testing.T) {
  1784  			m, _ := setupDeviceManager(t, nil, nil, socketName, topology)
  1785  			defer m.Stop()
  1786  			mimpl := m.(*wrappedManagerImpl)
  1787  
  1788  			wg := sync.WaitGroup{}
  1789  			wg.Add(2)
  1790  
  1791  			updated := atomic.Bool{}
  1792  			updated.Store(false)
  1793  			go func() {
  1794  				defer wg.Done()
  1795  				for i := 0; i < test.count; i++ {
  1796  					// simulate the device plugin to send device updates
  1797  					mimpl.genericDeviceUpdateCallback(testResourceName, devs)
  1798  				}
  1799  				updated.Store(true)
  1800  			}()
  1801  			go func() {
  1802  				defer wg.Done()
  1803  				for !updated.Load() {
  1804  					// When a data race occurs, golang will throw an error, and recover() cannot catch this error,
  1805  					// Such as: `throw("Concurrent map iteration and map writing")`.
  1806  					// When this test ends quietly, no data race error occurs.
  1807  					// Otherwise, the test process exits automatically and prints all goroutine call stacks.
  1808  					test.testfunc(mimpl)
  1809  				}
  1810  			}()
  1811  			wg.Wait()
  1812  		})
  1813  	}
  1814  }