diff --git a/pkg/framework/plugins/nodeutilization/highnodeutilization.go b/pkg/framework/plugins/nodeutilization/highnodeutilization.go index 38a3bc21d..6ea56f471 100644 --- a/pkg/framework/plugins/nodeutilization/highnodeutilization.go +++ b/pkg/framework/plugins/nodeutilization/highnodeutilization.go @@ -44,6 +44,7 @@ type HighNodeUtilization struct { underutilizationCriteria []interface{} resourceNames []v1.ResourceName targetThresholds api.ResourceThresholds + usageSnapshot *usageSnapshot } var _ frameworktypes.BalancePlugin = &HighNodeUtilization{} @@ -84,6 +85,7 @@ func NewHighNodeUtilization(args runtime.Object, handle frameworktypes.Handle) ( targetThresholds: targetThresholds, underutilizationCriteria: underutilizationCriteria, podFilter: podFilter, + usageSnapshot: newRequestedUsageSnapshot(resourceNames, handle.GetPodsAssignedToNodeFunc()), }, nil } @@ -94,9 +96,15 @@ func (h *HighNodeUtilization) Name() string { // Balance extension point implementation for the plugin func (h *HighNodeUtilization) Balance(ctx context.Context, nodes []*v1.Node) *frameworktypes.Status { + if err := h.usageSnapshot.capture(nodes); err != nil { + return &frameworktypes.Status{ + Err: fmt.Errorf("error getting node usage: %v", err), + } + } + sourceNodes, highNodes := classifyNodes( - getNodeUsage(nodes, h.resourceNames, h.handle.GetPodsAssignedToNodeFunc()), - getNodeThresholds(nodes, h.args.Thresholds, h.targetThresholds, h.resourceNames, h.handle.GetPodsAssignedToNodeFunc(), false), + getNodeUsage(nodes, h.usageSnapshot), + getNodeThresholds(nodes, h.args.Thresholds, h.targetThresholds, h.resourceNames, false, h.usageSnapshot), func(node *v1.Node, usage NodeUsage, threshold NodeThresholds) bool { return isNodeWithLowUtilization(usage, threshold.lowResourceThreshold) }, diff --git a/pkg/framework/plugins/nodeutilization/lownodeutilization.go b/pkg/framework/plugins/nodeutilization/lownodeutilization.go index ec52cd2d2..51055715f 100644 --- a/pkg/framework/plugins/nodeutilization/lownodeutilization.go +++ b/pkg/framework/plugins/nodeutilization/lownodeutilization.go @@ -43,6 +43,7 @@ type LowNodeUtilization struct { underutilizationCriteria []interface{} overutilizationCriteria []interface{} resourceNames []v1.ResourceName + usageSnapshot *usageSnapshot } var _ frameworktypes.BalancePlugin = &LowNodeUtilization{} @@ -85,13 +86,16 @@ func NewLowNodeUtilization(args runtime.Object, handle frameworktypes.Handle) (f return nil, fmt.Errorf("error initializing pod filter function: %v", err) } + resourceNames := getResourceNames(lowNodeUtilizationArgsArgs.Thresholds) + return &LowNodeUtilization{ handle: handle, args: lowNodeUtilizationArgsArgs, underutilizationCriteria: underutilizationCriteria, overutilizationCriteria: overutilizationCriteria, - resourceNames: getResourceNames(lowNodeUtilizationArgsArgs.Thresholds), + resourceNames: resourceNames, podFilter: podFilter, + usageSnapshot: newRequestedUsageSnapshot(resourceNames, handle.GetPodsAssignedToNodeFunc()), }, nil } @@ -102,9 +106,15 @@ func (l *LowNodeUtilization) Name() string { // Balance extension point implementation for the plugin func (l *LowNodeUtilization) Balance(ctx context.Context, nodes []*v1.Node) *frameworktypes.Status { + if err := l.usageSnapshot.capture(nodes); err != nil { + return &frameworktypes.Status{ + Err: fmt.Errorf("error getting node usage: %v", err), + } + } + lowNodes, sourceNodes := classifyNodes( - getNodeUsage(nodes, l.resourceNames, l.handle.GetPodsAssignedToNodeFunc()), - getNodeThresholds(nodes, l.args.Thresholds, l.args.TargetThresholds, l.resourceNames, l.handle.GetPodsAssignedToNodeFunc(), l.args.UseDeviationThresholds), + getNodeUsage(nodes, l.usageSnapshot), + getNodeThresholds(nodes, l.args.Thresholds, l.args.TargetThresholds, l.resourceNames, l.args.UseDeviationThresholds, l.usageSnapshot), // The node has to be schedulable (to be able to move workload there) func(node *v1.Node, usage NodeUsage, threshold NodeThresholds) bool { if nodeutil.IsNodeUnschedulable(node) { diff --git a/pkg/framework/plugins/nodeutilization/nodeutilization.go b/pkg/framework/plugins/nodeutilization/nodeutilization.go index b3a352aad..2d75ba314 100644 --- a/pkg/framework/plugins/nodeutilization/nodeutilization.go +++ b/pkg/framework/plugins/nodeutilization/nodeutilization.go @@ -74,18 +74,68 @@ func normalizePercentage(percent api.Percentage) api.Percentage { return percent } +type usageSnapshot struct { + resourceNames []v1.ResourceName + getPodsAssignedToNode podutil.GetPodsAssignedToNodeFunc + + _nodes []*v1.Node + _pods map[string][]*v1.Pod + _nodeUtilization map[string]map[v1.ResourceName]*resource.Quantity +} + +func newRequestedUsageSnapshot( + resourceNames []v1.ResourceName, + getPodsAssignedToNode podutil.GetPodsAssignedToNodeFunc, +) *usageSnapshot { + return &usageSnapshot{ + resourceNames: resourceNames, + getPodsAssignedToNode: getPodsAssignedToNode, + } +} + +func (s *usageSnapshot) nodeUtilization(node string) map[v1.ResourceName]*resource.Quantity { + return s._nodeUtilization[node] +} + +func (s *usageSnapshot) nodes() []*v1.Node { + return s._nodes +} + +func (s *usageSnapshot) pods(node string) []*v1.Pod { + return s._pods[node] +} + +func (s *usageSnapshot) capture(nodes []*v1.Node) error { + s._nodeUtilization = make(map[string]map[v1.ResourceName]*resource.Quantity) + s._pods = make(map[string][]*v1.Pod) + + for _, node := range nodes { + pods, err := podutil.ListPodsOnANode(node.Name, s.getPodsAssignedToNode, nil) + if err != nil { + klog.V(2).InfoS("Node will not be processed, error accessing its pods", "node", klog.KObj(node), "err", err) + continue + } + + // store the snapshot of pods from the same (or the closest) node utilization computation + s._pods[node.Name] = pods + s._nodeUtilization[node.Name] = nodeutil.NodeUtilization(pods, s.resourceNames) + } + + return nil +} + func getNodeThresholds( nodes []*v1.Node, lowThreshold, highThreshold api.ResourceThresholds, resourceNames []v1.ResourceName, - getPodsAssignedToNode podutil.GetPodsAssignedToNodeFunc, useDeviationThresholds bool, + usageSnapshot *usageSnapshot, ) map[string]NodeThresholds { nodeThresholdsMap := map[string]NodeThresholds{} averageResourceUsagePercent := api.ResourceThresholds{} if useDeviationThresholds { - averageResourceUsagePercent = averageNodeBasicresources(nodes, getPodsAssignedToNode, resourceNames) + averageResourceUsagePercent = averageNodeBasicresources(nodes, usageSnapshot) } for _, node := range nodes { @@ -121,22 +171,15 @@ func getNodeThresholds( func getNodeUsage( nodes []*v1.Node, - resourceNames []v1.ResourceName, - getPodsAssignedToNode podutil.GetPodsAssignedToNodeFunc, + usageSnapshot *usageSnapshot, ) []NodeUsage { var nodeUsageList []NodeUsage for _, node := range nodes { - pods, err := podutil.ListPodsOnANode(node.Name, getPodsAssignedToNode, nil) - if err != nil { - klog.V(2).InfoS("Node will not be processed, error accessing its pods", "node", klog.KObj(node), "err", err) - continue - } - nodeUsageList = append(nodeUsageList, NodeUsage{ node: node, - usage: nodeutil.NodeUtilization(pods, resourceNames), - allPods: pods, + usage: usageSnapshot.nodeUtilization(node.Name), + allPods: usageSnapshot.pods(node.Name), }) } @@ -437,17 +480,12 @@ func classifyPods(pods []*v1.Pod, filter func(pod *v1.Pod) bool) ([]*v1.Pod, []* return nonRemovablePods, removablePods } -func averageNodeBasicresources(nodes []*v1.Node, getPodsAssignedToNode podutil.GetPodsAssignedToNodeFunc, resourceNames []v1.ResourceName) api.ResourceThresholds { +func averageNodeBasicresources(nodes []*v1.Node, usageSnapshot *usageSnapshot) api.ResourceThresholds { total := api.ResourceThresholds{} average := api.ResourceThresholds{} numberOfNodes := len(nodes) for _, node := range nodes { - pods, err := podutil.ListPodsOnANode(node.Name, getPodsAssignedToNode, nil) - if err != nil { - numberOfNodes-- - continue - } - usage := nodeutil.NodeUtilization(pods, resourceNames) + usage := usageSnapshot.nodeUtilization(node.Name) nodeCapacity := node.Status.Capacity if len(node.Status.Allocatable) > 0 { nodeCapacity = node.Status.Allocatable