Skip to content

Commit

Permalink
nodeutiliation: create a usage snapshot
Browse files Browse the repository at this point in the history
  • Loading branch information
ingvagabund committed Oct 11, 2024
1 parent 89bd188 commit f064d56
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 24 deletions.
12 changes: 10 additions & 2 deletions pkg/framework/plugins/nodeutilization/highnodeutilization.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type HighNodeUtilization struct {
underutilizationCriteria []interface{}
resourceNames []v1.ResourceName
targetThresholds api.ResourceThresholds
usageSnapshot *usageSnapshot
}

var _ frameworktypes.BalancePlugin = &HighNodeUtilization{}
Expand Down Expand Up @@ -84,6 +85,7 @@ func NewHighNodeUtilization(args runtime.Object, handle frameworktypes.Handle) (
targetThresholds: targetThresholds,
underutilizationCriteria: underutilizationCriteria,
podFilter: podFilter,
usageSnapshot: newRequestedUsageSnapshot(resourceNames, handle.GetPodsAssignedToNodeFunc()),
}, nil
}

Expand All @@ -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)
},
Expand Down
16 changes: 13 additions & 3 deletions pkg/framework/plugins/nodeutilization/lownodeutilization.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type LowNodeUtilization struct {
underutilizationCriteria []interface{}
overutilizationCriteria []interface{}
resourceNames []v1.ResourceName
usageSnapshot *usageSnapshot
}

var _ frameworktypes.BalancePlugin = &LowNodeUtilization{}
Expand Down Expand Up @@ -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
}

Expand All @@ -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) {
Expand Down
76 changes: 57 additions & 19 deletions pkg/framework/plugins/nodeutilization/nodeutilization.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
})
}

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit f064d56

Please sign in to comment.