[cluster-autoscaler] Allow “custom” DaemonSet pods
* Create `utils/pod` package: pod kind detection functions. * Update DaemonSet Pod detection logic: introduce the annotation `cluster-autoscaler.kubernetes.io/daemonset-pod` to declare a Pod as a DaemonSet Pod. * Update `simulator` package to use the new `utils/pod` package function. * Cleanup `getRequiredPodsForNode()` function. Signed-off-by: cedric lamoriniere <cedric.lamoriniere@datadoghq.com>
This commit is contained in:
		
							parent
							
								
									7118ea8228
								
							
						
					
					
						commit
						f0fbf7a87a
					
				| 
						 | 
				
			
			@ -22,18 +22,18 @@ import (
 | 
			
		|||
	"math/rand"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/autoscaler/cluster-autoscaler/utils/drain"
 | 
			
		||||
	"k8s.io/autoscaler/cluster-autoscaler/utils/errors"
 | 
			
		||||
	"k8s.io/autoscaler/cluster-autoscaler/utils/glogx"
 | 
			
		||||
	"k8s.io/autoscaler/cluster-autoscaler/utils/gpu"
 | 
			
		||||
	kube_util "k8s.io/autoscaler/cluster-autoscaler/utils/kubernetes"
 | 
			
		||||
	pod_util "k8s.io/autoscaler/cluster-autoscaler/utils/pod"
 | 
			
		||||
	scheduler_util "k8s.io/autoscaler/cluster-autoscaler/utils/scheduler"
 | 
			
		||||
	"k8s.io/autoscaler/cluster-autoscaler/utils/tpu"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates"
 | 
			
		||||
 | 
			
		||||
	apiv1 "k8s.io/api/core/v1"
 | 
			
		||||
	policyv1 "k8s.io/api/policy/v1beta1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/api/resource"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates"
 | 
			
		||||
	schedulernodeinfo "k8s.io/kubernetes/pkg/scheduler/nodeinfo"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/klog"
 | 
			
		||||
| 
						 | 
				
			
			@ -201,11 +201,11 @@ func calculateUtilizationOfResource(node *apiv1.Node, nodeInfo *schedulernodeinf
 | 
			
		|||
	podsRequest := resource.MustParse("0")
 | 
			
		||||
	for _, pod := range nodeInfo.Pods() {
 | 
			
		||||
		// factor daemonset pods out of the utilization calculations
 | 
			
		||||
		if skipDaemonSetPods && isDaemonSet(pod) {
 | 
			
		||||
		if skipDaemonSetPods && pod_util.IsDaemonSetPod(pod) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		// factor mirror pods out of the utilization calculations
 | 
			
		||||
		if skipMirrorPods && drain.IsMirrorPod(pod) {
 | 
			
		||||
		if skipMirrorPods && pod_util.IsMirrorPod(pod) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		for _, container := range pod.Spec.Containers {
 | 
			
		||||
| 
						 | 
				
			
			@ -318,12 +318,3 @@ func shuffleNodes(nodes []*apiv1.Node) []*apiv1.Node {
 | 
			
		|||
	}
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isDaemonSet(pod *apiv1.Pod) bool {
 | 
			
		||||
	for _, ownerReference := range pod.ObjectMeta.OwnerReferences {
 | 
			
		||||
		if ownerReference.Kind == "DaemonSet" {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,7 +51,11 @@ func TestUtilization(t *testing.T) {
 | 
			
		|||
	daemonSetPod3 := BuildTestPod("p3", 100, 200000)
 | 
			
		||||
	daemonSetPod3.OwnerReferences = GenerateOwnerReferences("ds", "DaemonSet", "apps/v1", "")
 | 
			
		||||
 | 
			
		||||
	nodeInfo = schedulernodeinfo.NewNodeInfo(pod, pod, pod2, daemonSetPod3)
 | 
			
		||||
	daemonSetPod4 := BuildTestPod("p4", 100, 200000)
 | 
			
		||||
	daemonSetPod4.OwnerReferences = GenerateOwnerReferences("ds", "CustomDaemonSet", "crd/v1", "")
 | 
			
		||||
	daemonSetPod4.Annotations = map[string]string{"cluster-autoscaler.kubernetes.io/daemonset-pod": "true"}
 | 
			
		||||
 | 
			
		||||
	nodeInfo = schedulernodeinfo.NewNodeInfo(pod, pod, pod2, daemonSetPod3, daemonSetPod4)
 | 
			
		||||
	utilInfo, err = CalculateUtilization(node, nodeInfo, true, false, gpuLabel)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.InEpsilon(t, 2.0/10, utilInfo.Utilization, 0.01)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,6 @@ func FastGetPodsToMove(nodeInfo *schedulernodeinfo.NodeInfo, skipNodesWithSystem
 | 
			
		|||
	pods, err := drain.GetPodsForDeletionOnNodeDrain(
 | 
			
		||||
		nodeInfo.Pods(),
 | 
			
		||||
		pdbs,
 | 
			
		||||
		false,
 | 
			
		||||
		skipNodesWithSystemPods,
 | 
			
		||||
		skipNodesWithLocalStorage,
 | 
			
		||||
		false,
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +66,6 @@ func DetailedGetPodsForMove(nodeInfo *schedulernodeinfo.NodeInfo, skipNodesWithS
 | 
			
		|||
	pods, err := drain.GetPodsForDeletionOnNodeDrain(
 | 
			
		||||
		nodeInfo.Pods(),
 | 
			
		||||
		pdbs,
 | 
			
		||||
		false,
 | 
			
		||||
		skipNodesWithSystemPods,
 | 
			
		||||
		skipNodesWithLocalStorage,
 | 
			
		||||
		true,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,13 +17,11 @@ limitations under the License.
 | 
			
		|||
package simulator
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	apiv1 "k8s.io/api/core/v1"
 | 
			
		||||
	policyv1 "k8s.io/api/policy/v1beta1"
 | 
			
		||||
	"k8s.io/autoscaler/cluster-autoscaler/utils/drain"
 | 
			
		||||
	"k8s.io/autoscaler/cluster-autoscaler/utils/errors"
 | 
			
		||||
	schedulernodeinfo "k8s.io/kubernetes/pkg/scheduler/nodeinfo"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/autoscaler/cluster-autoscaler/utils/errors"
 | 
			
		||||
	pod_util "k8s.io/autoscaler/cluster-autoscaler/utils/pod"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// getRequiredPodsForNode returns a list of pods that would appear on the node if the
 | 
			
		||||
| 
						 | 
				
			
			@ -31,36 +29,8 @@ import (
 | 
			
		|||
// drain command to get the list.
 | 
			
		||||
func getRequiredPodsForNode(nodename string, podsForNodes map[string][]*apiv1.Pod) ([]*apiv1.Pod, errors.AutoscalerError) {
 | 
			
		||||
	allPods := podsForNodes[nodename]
 | 
			
		||||
	podsToRemoveList, err := drain.GetPodsForDeletionOnNodeDrain(
 | 
			
		||||
		allPods,
 | 
			
		||||
		[]*policyv1.PodDisruptionBudget{}, // PDBs are irrelevant when considering new node.
 | 
			
		||||
		true,                              // Force all removals.
 | 
			
		||||
		false,
 | 
			
		||||
		false,
 | 
			
		||||
		false, // Setting this to true requires listers to be not-null.
 | 
			
		||||
		nil,
 | 
			
		||||
		0,
 | 
			
		||||
		time.Now())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []*apiv1.Pod{}, errors.ToAutoscalerError(errors.InternalError, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	podsToRemoveMap := make(map[string]struct{})
 | 
			
		||||
	for _, pod := range podsToRemoveList {
 | 
			
		||||
		podsToRemoveMap[pod.SelfLink] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	podsOnNewNode := make([]*apiv1.Pod, 0)
 | 
			
		||||
	for _, pod := range allPods {
 | 
			
		||||
		if pod.DeletionTimestamp != nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, found := podsToRemoveMap[pod.SelfLink]; !found {
 | 
			
		||||
			podsOnNewNode = append(podsOnNewNode, pod)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return podsOnNewNode, nil
 | 
			
		||||
	return filterRequiredPodsForNode(allPods), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BuildNodeInfoForNode build a NodeInfo structure for the given node as if the node was just created.
 | 
			
		||||
| 
						 | 
				
			
			@ -75,3 +45,20 @@ func BuildNodeInfoForNode(node *apiv1.Node, podsForNodes map[string][]*apiv1.Pod
 | 
			
		|||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func filterRequiredPodsForNode(allPods []*apiv1.Pod) []*apiv1.Pod {
 | 
			
		||||
	var selectedPods []*apiv1.Pod
 | 
			
		||||
 | 
			
		||||
	for id, pod := range allPods {
 | 
			
		||||
		// Ignore pod in deletion phase
 | 
			
		||||
		if pod.DeletionTimestamp != nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if pod_util.IsMirrorPod(pod) || pod_util.IsDaemonSetPod(pod) {
 | 
			
		||||
			selectedPods = append(selectedPods, allPods[id])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return selectedPods
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,12 +18,17 @@ package simulator
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	apiv1 "k8s.io/api/core/v1"
 | 
			
		||||
	apiequality "k8s.io/apimachinery/pkg/api/equality"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/types"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	pod_util "k8s.io/autoscaler/cluster-autoscaler/utils/pod"
 | 
			
		||||
	. "k8s.io/autoscaler/cluster-autoscaler/utils/test"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestRequiredPodsForNode(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -73,3 +78,115 @@ func TestRequiredPodsForNode(t *testing.T) {
 | 
			
		|||
	assert.Equal(t, 1, len(pods))
 | 
			
		||||
	assert.Equal(t, "pod2", pods[0].Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_filterRequiredPodsForNode(t *testing.T) {
 | 
			
		||||
	nodeName := "node1"
 | 
			
		||||
	pod1 := &apiv1.Pod{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Namespace: "default",
 | 
			
		||||
			Name:      "pod1",
 | 
			
		||||
			SelfLink:  "pod1",
 | 
			
		||||
		},
 | 
			
		||||
		Spec: apiv1.PodSpec{
 | 
			
		||||
			NodeName: nodeName,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	// Manifest pod.
 | 
			
		||||
	mirrorPod := &apiv1.Pod{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name:      "mirrorPod",
 | 
			
		||||
			Namespace: "kube-system",
 | 
			
		||||
			SelfLink:  "mirrorPod",
 | 
			
		||||
			Annotations: map[string]string{
 | 
			
		||||
				types.ConfigMirrorAnnotationKey: "something",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Spec: apiv1.PodSpec{
 | 
			
		||||
			NodeName: nodeName,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	now := metav1.NewTime(time.Now())
 | 
			
		||||
	podDeleted := &apiv1.Pod{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Namespace: "default",
 | 
			
		||||
			Name:      "podDeleted",
 | 
			
		||||
			SelfLink:  "podDeleted",
 | 
			
		||||
			Annotations: map[string]string{
 | 
			
		||||
				types.ConfigMirrorAnnotationKey: "something",
 | 
			
		||||
			},
 | 
			
		||||
			DeletionTimestamp: &now,
 | 
			
		||||
		},
 | 
			
		||||
		Spec: apiv1.PodSpec{
 | 
			
		||||
			NodeName: nodeName,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	podDaemonset := &apiv1.Pod{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Namespace:       "default",
 | 
			
		||||
			Name:            "podDaemonset",
 | 
			
		||||
			SelfLink:        "podDaemonset",
 | 
			
		||||
			OwnerReferences: GenerateOwnerReferences("ds", "DaemonSet", "apps/v1", ""),
 | 
			
		||||
			Annotations: map[string]string{
 | 
			
		||||
				types.ConfigSourceAnnotationKey: types.FileSource,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Spec: apiv1.PodSpec{
 | 
			
		||||
			NodeName: nodeName,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	podDaemonsetAnnotation := &apiv1.Pod{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Namespace:       "default",
 | 
			
		||||
			Name:            "podDaemonset2",
 | 
			
		||||
			SelfLink:        "podDaemonset2",
 | 
			
		||||
			OwnerReferences: GenerateOwnerReferences("ds2", "CustomDaemonset", "crd/v1", ""),
 | 
			
		||||
			Annotations: map[string]string{
 | 
			
		||||
				pod_util.DaemonSetPodAnnotationKey: "true",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Spec: apiv1.PodSpec{
 | 
			
		||||
			NodeName: nodeName,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name      string
 | 
			
		||||
		inputPods []*apiv1.Pod
 | 
			
		||||
		want      []*apiv1.Pod
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:      "nil input pod list",
 | 
			
		||||
			inputPods: nil,
 | 
			
		||||
			want:      []*apiv1.Pod{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "should return only mirrorPod",
 | 
			
		||||
			inputPods: []*apiv1.Pod{pod1, mirrorPod},
 | 
			
		||||
			want:      []*apiv1.Pod{mirrorPod},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "should ignore podDeleted",
 | 
			
		||||
			inputPods: []*apiv1.Pod{pod1, mirrorPod, podDeleted},
 | 
			
		||||
			want:      []*apiv1.Pod{mirrorPod},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "should return daemonset pod",
 | 
			
		||||
			inputPods: []*apiv1.Pod{pod1, podDaemonset},
 | 
			
		||||
			want:      []*apiv1.Pod{podDaemonset},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "should return daemonset pods with",
 | 
			
		||||
			inputPods: []*apiv1.Pod{pod1, podDaemonset, podDaemonsetAnnotation},
 | 
			
		||||
			want:      []*apiv1.Pod{podDaemonset, podDaemonsetAnnotation},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := filterRequiredPodsForNode(tt.inputPods); !apiequality.Semantic.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("filterRequiredPodsForNode() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,10 +22,12 @@ import (
 | 
			
		|||
 | 
			
		||||
	apiv1 "k8s.io/api/core/v1"
 | 
			
		||||
	policyv1 "k8s.io/api/policy/v1beta1"
 | 
			
		||||
	apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/labels"
 | 
			
		||||
 | 
			
		||||
	kube_util "k8s.io/autoscaler/cluster-autoscaler/utils/kubernetes"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/types"
 | 
			
		||||
	pod_util "k8s.io/autoscaler/cluster-autoscaler/utils/pod"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +46,6 @@ const (
 | 
			
		|||
func GetPodsForDeletionOnNodeDrain(
 | 
			
		||||
	podList []*apiv1.Pod,
 | 
			
		||||
	pdbs []*policyv1.PodDisruptionBudget,
 | 
			
		||||
	deleteAll bool,
 | 
			
		||||
	skipNodesWithSystemPods bool,
 | 
			
		||||
	skipNodesWithLocalStorage bool,
 | 
			
		||||
	checkReferences bool, // Setting this to true requires client to be not-null.
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +63,7 @@ func GetPodsForDeletionOnNodeDrain(
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	for _, pod := range podList {
 | 
			
		||||
		if IsMirrorPod(pod) {
 | 
			
		||||
		if pod_util.IsMirrorPod(pod) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -107,24 +108,17 @@ func GetPodsForDeletionOnNodeDrain(
 | 
			
		|||
			} else {
 | 
			
		||||
				replicated = true
 | 
			
		||||
			}
 | 
			
		||||
		} else if refKind == "DaemonSet" {
 | 
			
		||||
			if checkReferences {
 | 
			
		||||
				ds, err := listers.DaemonSetLister().DaemonSets(controllerNamespace).Get(controllerRef.Name)
 | 
			
		||||
 | 
			
		||||
				// Assume the only reason for an error is because the DaemonSet is
 | 
			
		||||
				// gone/missing, not for any other cause.  TODO(mml): something more
 | 
			
		||||
				// sophisticated than this
 | 
			
		||||
				if err == nil && ds != nil {
 | 
			
		||||
					// Otherwise, treat daemonset-managed pods as unmanaged since
 | 
			
		||||
					// DaemonSet Controller currently ignores the unschedulable bit.
 | 
			
		||||
					// FIXME(mml): Add link to the issue concerning a proper way to drain
 | 
			
		||||
					// daemonset pods, probably using taints.
 | 
			
		||||
					daemonsetPod = true
 | 
			
		||||
				} else {
 | 
			
		||||
		} else if pod_util.IsDaemonSetPod(pod) {
 | 
			
		||||
			daemonsetPod = true
 | 
			
		||||
			// don't have listener for other DaemonSet kind
 | 
			
		||||
			// TODO: we should use a generic client for checking the reference.
 | 
			
		||||
			if checkReferences && refKind == "DaemonSet" {
 | 
			
		||||
				_, err := listers.DaemonSetLister().DaemonSets(controllerNamespace).Get(controllerRef.Name)
 | 
			
		||||
				if apierrors.IsNotFound(err) {
 | 
			
		||||
					return []*apiv1.Pod{}, fmt.Errorf("daemonset for %s/%s is not present, err: %v", pod.Namespace, pod.Name, err)
 | 
			
		||||
				} else if err != nil {
 | 
			
		||||
					return []*apiv1.Pod{}, fmt.Errorf("error when trying to get daemonset for %s/%s , err: %v", pod.Namespace, pod.Name, err)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				daemonsetPod = true
 | 
			
		||||
			}
 | 
			
		||||
		} else if refKind == "Job" {
 | 
			
		||||
			if checkReferences {
 | 
			
		||||
| 
						 | 
				
			
			@ -180,7 +174,7 @@ func GetPodsForDeletionOnNodeDrain(
 | 
			
		|||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !deleteAll && !safeToEvict && !terminal {
 | 
			
		||||
		if !safeToEvict && !terminal {
 | 
			
		||||
			if !replicated {
 | 
			
		||||
				return []*apiv1.Pod{}, fmt.Errorf("%s/%s is not replicated", pod.Namespace, pod.Name)
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -210,12 +204,6 @@ func ControllerRef(pod *apiv1.Pod) *metav1.OwnerReference {
 | 
			
		|||
	return metav1.GetControllerOf(pod)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsMirrorPod checks whether the pod is a mirror pod.
 | 
			
		||||
func IsMirrorPod(pod *apiv1.Pod) bool {
 | 
			
		||||
	_, found := pod.ObjectMeta.Annotations[types.ConfigMirrorAnnotationKey]
 | 
			
		||||
	return found
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// isPodTerminal checks whether the pod is in a terminal state.
 | 
			
		||||
func isPodTerminal(pod *apiv1.Pod) bool {
 | 
			
		||||
	// pod will never be restarted
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -103,6 +103,20 @@ func TestDrain(t *testing.T) {
 | 
			
		|||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cdsPod := &apiv1.Pod{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name:            "bar",
 | 
			
		||||
			Namespace:       "default",
 | 
			
		||||
			OwnerReferences: GenerateOwnerReferences(ds.Name, "CustomDaemonSet", "crd/v1", ""),
 | 
			
		||||
			Annotations: map[string]string{
 | 
			
		||||
				"cluster-autoscaler.kubernetes.io/daemonset-pod": "true",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Spec: apiv1.PodSpec{
 | 
			
		||||
			NodeName: "node",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	job := batchv1.Job{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name:      "job",
 | 
			
		||||
| 
						 | 
				
			
			@ -372,6 +386,13 @@ func TestDrain(t *testing.T) {
 | 
			
		|||
			expectFatal: false,
 | 
			
		||||
			expectPods:  []*apiv1.Pod{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			description: "DS-managed pod by a custom Daemonset",
 | 
			
		||||
			pods:        []*apiv1.Pod{cdsPod},
 | 
			
		||||
			pdbs:        []*policyv1.PodDisruptionBudget{},
 | 
			
		||||
			expectFatal: false,
 | 
			
		||||
			expectPods:  []*apiv1.Pod{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			description: "Job-managed pod",
 | 
			
		||||
			pods:        []*apiv1.Pod{jobPod},
 | 
			
		||||
| 
						 | 
				
			
			@ -540,8 +561,7 @@ func TestDrain(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
		registry := kube_util.NewListerRegistry(nil, nil, nil, nil, nil, dsLister, rcLister, jobLister, rsLister, ssLister)
 | 
			
		||||
 | 
			
		||||
		pods, err := GetPodsForDeletionOnNodeDrain(test.pods, test.pdbs,
 | 
			
		||||
			false, true, true, true, registry, 0, time.Now())
 | 
			
		||||
		pods, err := GetPodsForDeletionOnNodeDrain(test.pods, test.pdbs, true, true, true, registry, 0, time.Now())
 | 
			
		||||
 | 
			
		||||
		if test.expectFatal {
 | 
			
		||||
			if err == nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,62 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2017 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package pod
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/types"
 | 
			
		||||
 | 
			
		||||
	apiv1 "k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// DaemonSetPodAnnotationKey - annotation use to informs the cluster-autoscaler controller when a pod needs to be considered as a Daemonset's Pod.
 | 
			
		||||
	DaemonSetPodAnnotationKey = "cluster-autoscaler.kubernetes.io/daemonset-pod"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// IsDaemonSetPod returns true if the Pod should be considered as Pod managed by a DaemonSet
 | 
			
		||||
func IsDaemonSetPod(pod *apiv1.Pod) bool {
 | 
			
		||||
	controllerRef := metav1.GetControllerOf(pod)
 | 
			
		||||
	if controllerRef != nil && controllerRef.Kind == "DaemonSet" {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if val, ok := pod.Annotations[DaemonSetPodAnnotationKey]; ok && val == "true" {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsMirrorPod checks whether the pod is a mirror pod.
 | 
			
		||||
func IsMirrorPod(pod *apiv1.Pod) bool {
 | 
			
		||||
	if pod.ObjectMeta.Annotations == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	_, found := pod.ObjectMeta.Annotations[types.ConfigMirrorAnnotationKey]
 | 
			
		||||
	return found
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsStaticPod returns true if the pod is a static pod.
 | 
			
		||||
func IsStaticPod(pod *apiv1.Pod) bool {
 | 
			
		||||
	if pod.Annotations != nil {
 | 
			
		||||
		if source, ok := pod.Annotations[types.ConfigSourceAnnotationKey]; ok == true {
 | 
			
		||||
			return source != types.ApiserverSource
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,199 @@
 | 
			
		|||
/*
 | 
			
		||||
Copyright 2017 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package pod
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	apiv1 "k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestIsDaemonSetPod(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		pod  *apiv1.Pod
 | 
			
		||||
		want bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "Pod with empty OwnerRef",
 | 
			
		||||
			pod: &apiv1.Pod{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Name:      "foo",
 | 
			
		||||
					Namespace: "bar",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Pod with empty OwnerRef.Kind == DaemonSet",
 | 
			
		||||
			pod: &apiv1.Pod{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Name:      "foo",
 | 
			
		||||
					Namespace: "bar",
 | 
			
		||||
					OwnerReferences: []metav1.OwnerReference{
 | 
			
		||||
						{
 | 
			
		||||
							Controller: newBool(true),
 | 
			
		||||
							Kind:       "DaemonSet",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Pod with annotation but not with `DaemonSetPodAnnotationKey`",
 | 
			
		||||
			pod: &apiv1.Pod{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Name:        "foo",
 | 
			
		||||
					Namespace:   "bar",
 | 
			
		||||
					Annotations: make(map[string]string),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Pod with `DaemonSetPodAnnotationKey` annotation but wrong value",
 | 
			
		||||
			pod: &apiv1.Pod{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Name:      "foo",
 | 
			
		||||
					Namespace: "bar",
 | 
			
		||||
					Annotations: map[string]string{
 | 
			
		||||
						DaemonSetPodAnnotationKey: "bad value",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Pod with `DaemonSetPodAnnotationKey:true` annotation",
 | 
			
		||||
			pod: &apiv1.Pod{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Name:      "foo",
 | 
			
		||||
					Namespace: "bar",
 | 
			
		||||
					Annotations: map[string]string{
 | 
			
		||||
						DaemonSetPodAnnotationKey: "true",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := IsDaemonSetPod(tt.pod); got != tt.want {
 | 
			
		||||
				t.Errorf("IsDaemonSetPod() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newBool(b bool) *bool {
 | 
			
		||||
	return &b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsMirrorPod(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		pod  *apiv1.Pod
 | 
			
		||||
		want bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "pod with ConfigMirrorAnnotationKey",
 | 
			
		||||
			pod: &apiv1.Pod{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Name:      "foo",
 | 
			
		||||
					Namespace: "bar",
 | 
			
		||||
					Annotations: map[string]string{
 | 
			
		||||
						types.ConfigMirrorAnnotationKey: "",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "pod with without ConfigMirrorAnnotationKey",
 | 
			
		||||
			pod: &apiv1.Pod{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Name:      "foo",
 | 
			
		||||
					Namespace: "bar",
 | 
			
		||||
					Annotations: map[string]string{
 | 
			
		||||
						types.ConfigMirrorAnnotationKey: "",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "pod with nil Annotations map",
 | 
			
		||||
			pod: &apiv1.Pod{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Name:      "foo",
 | 
			
		||||
					Namespace: "bar",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := IsMirrorPod(tt.pod); got != tt.want {
 | 
			
		||||
				t.Errorf("IsMirrorPod() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsStaticPod(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		pod  *apiv1.Pod
 | 
			
		||||
		want bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "not a static pod",
 | 
			
		||||
			pod: &apiv1.Pod{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Name:      "foo",
 | 
			
		||||
					Namespace: "bar",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "is a static pod",
 | 
			
		||||
			pod: &apiv1.Pod{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Name:      "foo",
 | 
			
		||||
					Namespace: "bar",
 | 
			
		||||
					Annotations: map[string]string{
 | 
			
		||||
						types.ConfigSourceAnnotationKey: types.FileSource,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := IsStaticPod(tt.pod); got != tt.want {
 | 
			
		||||
				t.Errorf("IsStaticPod() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue