diff --git a/pkg/controller/endpointslice/topologycache/topologycache.go b/pkg/controller/endpointslice/topologycache/topologycache.go index b9d0c15f73b..a286fd16011 100644 --- a/pkg/controller/endpointslice/topologycache/topologycache.go +++ b/pkg/controller/endpointslice/topologycache/topologycache.go @@ -170,9 +170,15 @@ func (t *TopologyCache) SetNodes(nodes []*v1.Node) { totalCPU := resource.Quantity{} for _, node := range nodes { - if !NodeReady(node.Status) { + if hasExcludedLabels(node.Labels) { + klog.V(2).Infof("Ignoring node %s because it has an excluded label", node.Name) continue } + if !NodeReady(node.Status) { + klog.V(2).Infof("Ignoring node %s because it is not ready: %v", node.Name, node.Status.Conditions) + continue + } + nodeCPU := node.Status.Allocatable.Cpu() zone, ok := node.Labels[v1.LabelTopologyZone] @@ -254,3 +260,18 @@ func (t *TopologyCache) getAllocations(numEndpoints int) map[string]Allocation { return allocations } + +// Nodes with any of these labels set to any value will be excluded from +// topology capacity calculations. +func hasExcludedLabels(labels map[string]string) bool { + if len(labels) == 0 { + return false + } + if _, ok := labels["node-role.kubernetes.io/control-plane"]; ok { + return true + } + if _, ok := labels["node-role.kubernetes.io/master"]; ok { + return true + } + return false +} diff --git a/pkg/controller/endpointslice/topologycache/topologycache_test.go b/pkg/controller/endpointslice/topologycache/topologycache_test.go index 6f8bb369a9f..febbdc37a71 100644 --- a/pkg/controller/endpointslice/topologycache/topologycache_test.go +++ b/pkg/controller/endpointslice/topologycache/topologycache_test.go @@ -20,7 +20,7 @@ import ( "reflect" "testing" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" discovery "k8s.io/api/discovery/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -290,9 +290,10 @@ func TestAddHints(t *testing.T) { func TestSetNodes(t *testing.T) { type nodeInfo struct { - zone string - cpu resource.Quantity - ready v1.ConditionStatus + zone string + cpu resource.Quantity + ready v1.ConditionStatus + labels map[string]string } testCases := []struct { @@ -348,6 +349,16 @@ func TestSetNodes(t *testing.T) { expectSufficientNodeInfo: false, expectedCPUByZone: nil, expectedRatios: nil, + }, { + name: "2 zones, control plane node in 1, ready node in 1", + nodes: []nodeInfo{ + {zone: "zone-a", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue}, + {zone: "zone-b", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue, + labels: map[string]string{"node-role.kubernetes.io/control-plane": ""}}, + }, + expectSufficientNodeInfo: false, + expectedCPUByZone: nil, + expectedRatios: nil, }, { name: "2 zones, unready node in 1, ready node in 2", nodes: []nodeInfo{ @@ -364,6 +375,23 @@ func TestSetNodes(t *testing.T) { "zone-a": 0.5, "zone-b": 0.5, }, + }, { + name: "2 zones, control plane node in 1, ready node in 2", + nodes: []nodeInfo{ + {zone: "zone-a", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue}, + {zone: "zone-b", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue}, + {zone: "zone-b", cpu: resource.MustParse("1000m"), ready: v1.ConditionTrue, + labels: map[string]string{"node-role.kubernetes.io/control-plane": ""}}, + }, + expectSufficientNodeInfo: true, + expectedCPUByZone: map[string]*resource.Quantity{ + "zone-a": resource.NewQuantity(1, resource.BinarySI), + "zone-b": resource.NewQuantity(1, resource.BinarySI), + }, + expectedRatios: map[string]float64{ + "zone-a": 0.5, + "zone-b": 0.5, + }, }, { name: "3 zones, 4 nodes in 1, 2 nodes in 1, 1 node in 1", nodes: []nodeInfo{ @@ -393,7 +421,10 @@ func TestSetNodes(t *testing.T) { cache := NewTopologyCache() nodes := make([]*v1.Node, 0, len(tc.nodes)) for _, node := range tc.nodes { - labels := map[string]string{} + labels := node.labels + if labels == nil { + labels = map[string]string{} + } if node.zone != "" { labels[v1.LabelTopologyZone] = node.zone }