Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1.25] node: device-mgr: Fix recovery flow by ensuring healthy devices exist and pre-allocated devices are healthy #117738

Merged
merged 18 commits into from May 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
dd9dbcc
e2e: node: unify sample device plugin utilities
ffromani Feb 21, 2023
3a9c226
node: device-mgr: sample device plugin: control registration process
swatisehgal Dec 23, 2022
02e1fff
node: device-mgr: sample device plugin: manifest to avoid registration
swatisehgal Dec 23, 2022
7d1756a
test: Fix path to e2e node sample device plugin
bobbypage Mar 3, 2023
d00f41a
node: device-plugins: e2e: Refactor parse log to return string and error
swatisehgal Mar 13, 2023
6bae194
node: device-plugins: e2e: s/devLen/expectedSampleDevsAmount
swatisehgal Mar 13, 2023
20f6635
node: device-plugin: e2e: Annotate device check with error message
swatisehgal Mar 13, 2023
b9ecf98
node: device-plugin: e2e: Isolate test to pod restart scenario
swatisehgal Mar 9, 2023
60d81f0
node: device-plugin: e2e: Update test description to make it explicit
swatisehgal Mar 13, 2023
e53a718
node: device-plugin: e2e: Provide sleep intervals via constants
swatisehgal Mar 13, 2023
57b644b
node: device-plugin: e2e: Add test case for kubelet restart
swatisehgal Mar 13, 2023
337e6e7
node: device-mgr: Handle recovery by checking if healthy devices exist
swatisehgal Dec 21, 2022
4eb2808
node: device-mgr: e2e: Implement End to end test
swatisehgal Dec 24, 2022
6c0c91e
node: device-mgr: e2e: Update the e2e test to reproduce issue:109595
swatisehgal Dec 24, 2022
9fc2d5c
node: device-mgr: e2e: adapt to sample device plugin refactoring
swatisehgal Feb 27, 2023
b50d53f
node: device-plugin: e2e: Capture pod admission failure
swatisehgal Apr 26, 2023
4ba6f87
node: device-plugin: add node reboot test scenario
swatisehgal Apr 26, 2023
7f1809d
node: device-plugin: e2e: Additional test cases
swatisehgal Apr 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 19 additions & 5 deletions pkg/kubelet/cm/devicemanager/manager.go
Expand Up @@ -536,15 +536,29 @@ func (m *ManagerImpl) devicesToAllocate(podUID, contName, resource string, requi
return nil, fmt.Errorf("pod %q container %q changed request for resource %q from %d to %d", string(podUID), contName, resource, devices.Len(), required)
}
}

klog.V(3).InfoS("Need devices to allocate for pod", "deviceNumber", needed, "resourceName", resource, "podUID", string(podUID), "containerName", contName)
healthyDevices, hasRegistered := m.healthyDevices[resource]

// Check if resource registered with devicemanager
if !hasRegistered {
return nil, fmt.Errorf("cannot allocate unregistered device %s", resource)
}

// Check if registered resource has healthy devices
if healthyDevices.Len() == 0 {
return nil, fmt.Errorf("no healthy devices present; cannot allocate unhealthy devices %s", resource)
}

// Check if all the previously allocated devices are healthy
if !healthyDevices.IsSuperset(devices) {
return nil, fmt.Errorf("previously allocated devices are no longer healthy; cannot allocate unhealthy devices %s", resource)
}

if needed == 0 {
// No change, no work.
return nil, nil
}
klog.V(3).InfoS("Need devices to allocate for pod", "deviceNumber", needed, "resourceName", resource, "podUID", string(podUID), "containerName", contName)
// Check if resource registered with devicemanager
if _, ok := m.healthyDevices[resource]; !ok {
return nil, fmt.Errorf("can't allocate unregistered device %s", resource)
}

// Declare the list of allocated devices.
// This will be populated and returned below.
Expand Down
96 changes: 96 additions & 0 deletions pkg/kubelet/cm/devicemanager/manager_test.go
Expand Up @@ -986,6 +986,102 @@ func TestPodContainerDeviceAllocation(t *testing.T) {

}

func TestPodContainerDeviceToAllocate(t *testing.T) {
resourceName1 := "domain1.com/resource1"
resourceName2 := "domain2.com/resource2"
resourceName3 := "domain2.com/resource3"
as := require.New(t)
tmpDir, err := os.MkdirTemp("", "checkpoint")
as.Nil(err)
defer os.RemoveAll(tmpDir)

testManager := &ManagerImpl{
endpoints: make(map[string]endpointInfo),
healthyDevices: make(map[string]sets.String),
unhealthyDevices: make(map[string]sets.String),
allocatedDevices: make(map[string]sets.String),
podDevices: newPodDevices(),
}

testManager.podDevices.insert("pod1", "con1", resourceName1,
constructDevices([]string{"dev1", "dev2"}),
constructAllocResp(map[string]string{"/dev/r2dev1": "/dev/r2dev1", "/dev/r2dev2": "/dev/r2dev2"},
map[string]string{"/home/r2lib1": "/usr/r2lib1"},
map[string]string{"r2devices": "dev1 dev2"}))
testManager.podDevices.insert("pod2", "con2", resourceName2,
checkpoint.DevicesPerNUMA{nodeWithoutTopology: []string{"dev5"}},
constructAllocResp(map[string]string{"/dev/r1dev5": "/dev/r1dev5"},
map[string]string{"/home/r1lib1": "/usr/r1lib1"}, map[string]string{}))
testManager.podDevices.insert("pod3", "con3", resourceName3,
checkpoint.DevicesPerNUMA{nodeWithoutTopology: []string{"dev5"}},
constructAllocResp(map[string]string{"/dev/r1dev5": "/dev/r1dev5"},
map[string]string{"/home/r1lib1": "/usr/r1lib1"}, map[string]string{}))

// no healthy devices for resourceName1 and devices corresponding to
// resource2 are intentionally omitted to simulate that the resource
// hasn't been registered.
testManager.healthyDevices[resourceName1] = sets.NewString()
testManager.healthyDevices[resourceName3] = sets.NewString()
// dev5 is no longer in the list of healthy devices
testManager.healthyDevices[resourceName3].Insert("dev7")
testManager.healthyDevices[resourceName3].Insert("dev8")

testCases := []struct {
description string
podUID string
contName string
resource string
required int
reusableDevices sets.String
expectedAllocatedDevices sets.String
expErr error
}{
{
description: "Admission error in case no healthy devices to allocate present",
podUID: "pod1",
contName: "con1",
resource: resourceName1,
required: 2,
reusableDevices: sets.NewString(),
expectedAllocatedDevices: nil,
expErr: fmt.Errorf("no healthy devices present; cannot allocate unhealthy devices %s", resourceName1),
},
{
description: "Admission error in case resource is not registered",
podUID: "pod2",
contName: "con2",
resource: resourceName2,
required: 1,
reusableDevices: sets.NewString(),
expectedAllocatedDevices: nil,
expErr: fmt.Errorf("cannot allocate unregistered device %s", resourceName2),
},
{
description: "Admission error in case resource not devices previously allocated no longer healthy",
podUID: "pod3",
contName: "con3",
resource: resourceName3,
required: 1,
reusableDevices: sets.NewString(),
expectedAllocatedDevices: nil,
expErr: fmt.Errorf("previously allocated devices are no longer healthy; cannot allocate unhealthy devices %s", resourceName3),
},
}

for _, testCase := range testCases {
allocDevices, err := testManager.devicesToAllocate(testCase.podUID, testCase.contName, testCase.resource, testCase.required, testCase.reusableDevices)
if !reflect.DeepEqual(err, testCase.expErr) {
t.Errorf("devicePluginManager error (%v). expected error: %v but got: %v",
testCase.description, testCase.expErr, err)
}
if !reflect.DeepEqual(allocDevices, testCase.expectedAllocatedDevices) {
t.Errorf("devicePluginManager error (%v). expected error: %v but got: %v",
testCase.description, testCase.expectedAllocatedDevices, allocDevices)
}
}

}

func TestGetDeviceRunContainerOptions(t *testing.T) {
res1 := TestResource{
resourceName: "domain1.com/resource1",
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/testing-manifests/embed.go
Expand Up @@ -22,7 +22,7 @@ import (
e2etestfiles "k8s.io/kubernetes/test/e2e/framework/testfiles"
)

//go:embed cluster-dns flexvolume guestbook kubectl sample-device-plugin.yaml scheduling/nvidia-driver-installer.yaml statefulset storage-csi
//go:embed cluster-dns flexvolume guestbook kubectl sample-device-plugin scheduling/nvidia-driver-installer.yaml statefulset storage-csi
var e2eTestingManifestsFS embed.FS

func GetE2ETestingManifestsFS() e2etestfiles.EmbeddedFileSource {
Expand Down
@@ -0,0 +1,52 @@
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: sample-device-plugin-beta
namespace: kube-system
labels:
k8s-app: sample-device-plugin
spec:
selector:
matchLabels:
k8s-app: sample-device-plugin
template:
metadata:
labels:
k8s-app: sample-device-plugin
annotations:
spec:
priorityClassName: system-node-critical
tolerations:
- operator: "Exists"
effect: "NoExecute"
- operator: "Exists"
effect: "NoSchedule"
volumes:
- name: device-plugin
hostPath:
path: /var/lib/kubelet/device-plugins
- name: plugins-registry-probe-mode
hostPath:
path: /var/lib/kubelet/plugins_registry
- name: dev
hostPath:
path: /dev
containers:
- image: registry.k8s.io/e2e-test-images/sample-device-plugin:1.5
name: sample-device-plugin
env:
- name: PLUGIN_SOCK_DIR
value: "/var/lib/kubelet/device-plugins"
- name: REGISTER_CONTROL_FILE
value: "/var/lib/kubelet/device-plugins/sample/registration"
securityContext:
privileged: true
volumeMounts:
- name: device-plugin
mountPath: /var/lib/kubelet/device-plugins
- name: plugins-registry-probe-mode
mountPath: /var/lib/kubelet/plugins_registry
- name: dev
mountPath: /dev
updateStrategy:
type: RollingUpdate