2015-08-05 00:19:05 +00:00
|
|
|
package structs
|
|
|
|
|
2015-09-07 22:08:50 +00:00
|
|
|
import (
|
|
|
|
crand "crypto/rand"
|
|
|
|
"fmt"
|
|
|
|
"math"
|
|
|
|
)
|
2015-08-13 18:54:59 +00:00
|
|
|
|
2015-08-05 00:19:05 +00:00
|
|
|
// RemoveAllocs is used to remove any allocs with the given IDs
|
|
|
|
// from the list of allocations
|
2015-08-25 23:52:56 +00:00
|
|
|
func RemoveAllocs(alloc []*Allocation, remove []*Allocation) []*Allocation {
|
2015-08-05 00:19:05 +00:00
|
|
|
// Convert remove into a set
|
|
|
|
removeSet := make(map[string]struct{})
|
2015-08-25 23:52:56 +00:00
|
|
|
for _, remove := range remove {
|
|
|
|
removeSet[remove.ID] = struct{}{}
|
2015-08-05 00:19:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
n := len(alloc)
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
if _, ok := removeSet[alloc[i].ID]; ok {
|
|
|
|
alloc[i], alloc[n-1] = alloc[n-1], nil
|
|
|
|
i--
|
|
|
|
n--
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
alloc = alloc[:n]
|
|
|
|
return alloc
|
|
|
|
}
|
2015-08-05 00:28:19 +00:00
|
|
|
|
2015-08-23 01:27:51 +00:00
|
|
|
// FilterTerminalAllocs filters out all allocations in a terminal state
|
|
|
|
func FilterTerminalAllocs(allocs []*Allocation) []*Allocation {
|
|
|
|
n := len(allocs)
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
if allocs[i].TerminalStatus() {
|
|
|
|
allocs[i], allocs[n-1] = allocs[n-1], nil
|
|
|
|
i--
|
|
|
|
n--
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return allocs[:n]
|
|
|
|
}
|
|
|
|
|
2015-08-05 00:28:19 +00:00
|
|
|
// PortsOvercommited checks if any ports are over-committed.
|
|
|
|
// This does not handle CIDR subsets, and computes for the entire
|
|
|
|
// CIDR block currently.
|
|
|
|
func PortsOvercommited(r *Resources) bool {
|
|
|
|
for _, net := range r.Networks {
|
|
|
|
ports := make(map[int]struct{})
|
|
|
|
for _, port := range net.ReservedPorts {
|
|
|
|
if _, ok := ports[port]; ok {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
ports[port] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2015-08-05 00:48:24 +00:00
|
|
|
|
|
|
|
// AllocsFit checks if a given set of allocations will fit on a node
|
2015-08-13 18:54:59 +00:00
|
|
|
func AllocsFit(node *Node, allocs []*Allocation) (bool, *Resources, error) {
|
2015-08-05 00:48:24 +00:00
|
|
|
// Compute the utilization from zero
|
|
|
|
used := new(Resources)
|
|
|
|
for _, net := range node.Resources.Networks {
|
|
|
|
used.Networks = append(used.Networks, &NetworkResource{
|
|
|
|
Public: net.Public,
|
|
|
|
CIDR: net.CIDR,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the reserved resources of the node
|
|
|
|
if node.Reserved != nil {
|
|
|
|
if err := used.Add(node.Reserved); err != nil {
|
2015-08-13 18:54:59 +00:00
|
|
|
return false, nil, err
|
2015-08-05 00:48:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// For each alloc, add the resources
|
|
|
|
for _, alloc := range allocs {
|
|
|
|
if err := used.Add(alloc.Resources); err != nil {
|
2015-08-13 18:54:59 +00:00
|
|
|
return false, nil, err
|
2015-08-05 00:48:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that the node resources are a super set of those
|
|
|
|
// that are being allocated
|
|
|
|
if !node.Resources.Superset(used) {
|
2015-08-13 18:54:59 +00:00
|
|
|
return false, used, nil
|
2015-08-05 00:48:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure ports are not over commited
|
|
|
|
if PortsOvercommited(used) {
|
2015-08-13 18:54:59 +00:00
|
|
|
return false, used, nil
|
2015-08-05 00:48:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Allocations fit!
|
2015-08-13 18:54:59 +00:00
|
|
|
return true, used, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ScoreFit is used to score the fit based on the Google work published here:
|
|
|
|
// http://www.columbia.edu/~cs2035/courses/ieor4405.S13/datacenter_scheduling.ppt
|
|
|
|
// This is equivalent to their BestFit v3
|
|
|
|
func ScoreFit(node *Node, util *Resources) float64 {
|
|
|
|
// Determine the node availability
|
|
|
|
nodeCpu := node.Resources.CPU
|
|
|
|
if node.Reserved != nil {
|
|
|
|
nodeCpu -= node.Reserved.CPU
|
|
|
|
}
|
|
|
|
nodeMem := float64(node.Resources.MemoryMB)
|
|
|
|
if node.Reserved != nil {
|
|
|
|
nodeMem -= float64(node.Reserved.MemoryMB)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute the free percentage
|
|
|
|
freePctCpu := 1 - (util.CPU / nodeCpu)
|
|
|
|
freePctRam := 1 - (float64(util.MemoryMB) / nodeMem)
|
|
|
|
|
|
|
|
// Total will be "maximized" the smaller the value is.
|
|
|
|
// At 100% utilization, the total is 2, while at 0% util it is 20.
|
|
|
|
total := math.Pow(10, freePctCpu) + math.Pow(10, freePctRam)
|
|
|
|
|
|
|
|
// Invert so that the "maximized" total represents a high-value
|
|
|
|
// score. Because the floor is 20, we simply use that as an anchor.
|
|
|
|
// This means at a perfect fit, we return 18 as the score.
|
|
|
|
score := 20.0 - total
|
|
|
|
|
|
|
|
// Bound the score, just in case
|
|
|
|
// If the score is over 18, that means we've overfit the node.
|
|
|
|
if score > 18.0 {
|
|
|
|
score = 18.0
|
|
|
|
} else if score < 0 {
|
|
|
|
score = 0
|
|
|
|
}
|
|
|
|
return score
|
2015-08-05 00:48:24 +00:00
|
|
|
}
|
2015-09-07 22:08:50 +00:00
|
|
|
|
|
|
|
// GenerateUUID is used to generate a random UUID
|
|
|
|
func GenerateUUID() string {
|
|
|
|
buf := make([]byte, 16)
|
|
|
|
if _, err := crand.Read(buf); err != nil {
|
|
|
|
panic(fmt.Errorf("failed to read random bytes: %v", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x",
|
|
|
|
buf[0:4],
|
|
|
|
buf[4:6],
|
|
|
|
buf[6:8],
|
|
|
|
buf[8:10],
|
|
|
|
buf[10:16])
|
|
|
|
}
|