Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions backend/cmd/headlamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import (
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/trace"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sruntime "k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -2707,8 +2708,7 @@ func (c *HeadlampConfig) drainNode(clientset kubernetes.Interface, nodeName stri
var deleteErrors []string

for _, pod := range pods.Items {
// ignore daemonsets
if pod.Labels["kubernetes.io/created-by"] == "daemonset-controller" {
if isDaemonSetPod(pod) {
continue
}

Expand All @@ -2731,6 +2731,22 @@ func (c *HeadlampConfig) drainNode(clientset kubernetes.Interface, nodeName stri
}()
}

// isDaemonSetPod reports whether pod should be treated as managed by a DaemonSet.
// It supports both the legacy daemonset-controller label and modern owner references.
func isDaemonSetPod(pod corev1.Pod) bool {
Comment thread
harrshita123 marked this conversation as resolved.
if pod.Labels["kubernetes.io/created-by"] == "daemonset-controller" {
return true
}

for _, owner := range pod.OwnerReferences {
if owner.Kind == "DaemonSet" && (owner.APIVersion == "" || strings.HasPrefix(owner.APIVersion, "apps/")) {
Comment on lines +2735 to +2742
return true
}
}

return false
}

/*
* Handle node drain status
Since node drain is an async operation, we need to poll for the status of the drain operation
Expand Down
27 changes: 26 additions & 1 deletion backend/cmd/headlamp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,13 @@ func TestDrainNodePodDeletionFailure(t *testing.T) { //nolint:funlen
ObjectMeta: metav1.ObjectMeta{
Name: "pod-daemonset",
Namespace: "default",
Labels: map[string]string{"kubernetes.io/created-by": "daemonset-controller"},
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "apps/v1",
Kind: "DaemonSet",
Name: "node-agent",
},
},
},
Spec: corev1.PodSpec{
NodeName: "test-node",
Expand Down Expand Up @@ -721,6 +727,7 @@ func TestDrainNodePodDeletionFailure(t *testing.T) { //nolint:funlen
assert.True(t, strings.HasPrefix(status, "error:"),
"expected error status, got: %s", status)
assert.Contains(t, status, "failed to delete")
assert.ElementsMatch(t, []string{"pod-ok", "pod-fail"}, deletedPodNames(fakeClient.Actions()))
}

func TestDrainNodeAllPodsDeletedSuccessfully(t *testing.T) {
Expand Down Expand Up @@ -782,6 +789,24 @@ func TestDrainNodeAllPodsDeletedSuccessfully(t *testing.T) {
require.True(t, ok)

assert.Equal(t, "success", status)

assert.ElementsMatch(t, []string{"pod-1"}, deletedPodNames(fakeClient.Actions()))
}

// deletedPodNames returns the pod names from delete actions recorded by the fake client.
func deletedPodNames(actions []k8stesting.Action) []string {
Comment thread
harrshita123 marked this conversation as resolved.
deletedPods := []string{}

for _, action := range actions {
if action.GetVerb() != "delete" || action.GetResource().Resource != "pods" {
continue
}

deleteAction := action.(k8stesting.DeleteAction)
deletedPods = append(deletedPods, deleteAction.GetName())
}

return deletedPods
}

func TestDeletePlugin(t *testing.T) {
Expand Down
Loading