diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index c420a3053d..cba865ba8a 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -67,6 +67,9 @@ const ( // This label is feature-gated in kubernetes/kubernetes but we do not have feature gates // This will need to be updated after the end of the alpha LabelNodeRoleExcludeBalancer = "alpha.service-controller.kubernetes.io/exclude-balancer" + // ToBeDeletedTaint is the taint that the autoscaler adds when a node is scheduled to be deleted + // https://github.com/kubernetes/autoscaler/blob/cluster-autoscaler-0.5.2/cluster-autoscaler/utils/deletetaint/delete.go#L33 + ToBeDeletedTaint = "ToBeDeletedByClusterAutoscaler" ) // FakeGoogleAPIForbiddenErr creates a Forbidden error with type googleapi.Error @@ -319,6 +322,13 @@ func GetNodeConditionPredicate() listers.NodeConditionPredicate { return false } + // Get all nodes that have a taint with NoSchedule effect + for _, taint := range node.Spec.Taints { + if taint.Key == ToBeDeletedTaint { + return false + } + } + // As of 1.6, we will taint the master, but not necessarily mark it unschedulable. // Recognize nodes labeled as master, and filter them also, as we were doing previously. if _, hasMasterRoleLabel := node.Labels[LabelNodeRoleMaster]; hasMasterRoleLabel { diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index cbdedd7e9e..43ca196494 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -17,8 +17,11 @@ limitations under the License. package utils import ( + "fmt" "testing" + "time" + api_v1 "k8s.io/api/core/v1" extensions "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" @@ -452,6 +455,69 @@ func TestTraverseIngressBackends(t *testing.T) { } } +func TestGetNodeConditionPredicate(t *testing.T) { + tests := []struct { + node api_v1.Node + expectAccept bool + name string + }{ + { + node: api_v1.Node{}, + expectAccept: false, + name: "empty", + }, + { + node: api_v1.Node{ + Status: api_v1.NodeStatus{ + Conditions: []api_v1.NodeCondition{ + {Type: api_v1.NodeReady, Status: api_v1.ConditionTrue}, + }, + }, + }, + expectAccept: true, + name: "basic", + }, + { + node: api_v1.Node{ + Spec: api_v1.NodeSpec{Unschedulable: true}, + Status: api_v1.NodeStatus{ + Conditions: []api_v1.NodeCondition{ + {Type: api_v1.NodeReady, Status: api_v1.ConditionTrue}, + }, + }, + }, + expectAccept: false, + name: "unschedulable", + }, { + node: api_v1.Node{ + Spec: api_v1.NodeSpec{ + Taints: []api_v1.Taint{ + api_v1.Taint{ + Key: ToBeDeletedTaint, + Value: fmt.Sprint(time.Now().Unix()), + Effect: api_v1.TaintEffectNoSchedule, + }, + }, + }, + Status: api_v1.NodeStatus{ + Conditions: []api_v1.NodeCondition{ + {Type: api_v1.NodeReady, Status: api_v1.ConditionTrue}, + }, + }, + }, + expectAccept: false, + name: "ToBeDeletedByClusterAutoscaler-taint", + }, + } + pred := GetNodeConditionPredicate() + for _, test := range tests { + accept := pred(&test.node) + if accept != test.expectAccept { + t.Errorf("Test failed for %s, expected %v, saw %v", test.name, test.expectAccept, accept) + } + } +} + func getTestIngress() { return }