github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/scheduler/rank.go (about) 1 package scheduler 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/nomad/nomad/structs" 7 ) 8 9 // Rank is used to provide a score and various ranking metadata 10 // along with a node when iterating. This state can be modified as 11 // various rank methods are applied. 12 type RankedNode struct { 13 Node *structs.Node 14 Score float64 15 TaskResources map[string]*structs.Resources 16 17 // Allocs is used to cache the proposed allocations on the 18 // node. This can be shared between iterators that require it. 19 Proposed []*structs.Allocation 20 } 21 22 func (r *RankedNode) GoString() string { 23 return fmt.Sprintf("<Node: %s Score: %0.3f>", r.Node.ID, r.Score) 24 } 25 26 func (r *RankedNode) ProposedAllocs(ctx Context) ([]*structs.Allocation, error) { 27 if r.Proposed != nil { 28 return r.Proposed, nil 29 } 30 31 p, err := ctx.ProposedAllocs(r.Node.ID) 32 if err != nil { 33 return nil, err 34 } 35 r.Proposed = p 36 return p, nil 37 } 38 39 func (r *RankedNode) SetTaskResources(task *structs.Task, 40 resource *structs.Resources) { 41 if r.TaskResources == nil { 42 r.TaskResources = make(map[string]*structs.Resources) 43 } 44 r.TaskResources[task.Name] = resource 45 } 46 47 // RankFeasibleIterator is used to iteratively yield nodes along 48 // with ranking metadata. The iterators may manage some state for 49 // performance optimizations. 50 type RankIterator interface { 51 // Next yields a ranked option or nil if exhausted 52 Next() *RankedNode 53 54 // Reset is invoked when an allocation has been placed 55 // to reset any stale state. 56 Reset() 57 } 58 59 // FeasibleRankIterator is used to consume from a FeasibleIterator 60 // and return an unranked node with base ranking. 61 type FeasibleRankIterator struct { 62 ctx Context 63 source FeasibleIterator 64 } 65 66 // NewFeasibleRankIterator is used to return a new FeasibleRankIterator 67 // from a FeasibleIterator source. 68 func NewFeasibleRankIterator(ctx Context, source FeasibleIterator) *FeasibleRankIterator { 69 iter := &FeasibleRankIterator{ 70 ctx: ctx, 71 source: source, 72 } 73 return iter 74 } 75 76 func (iter *FeasibleRankIterator) Next() *RankedNode { 77 option := iter.source.Next() 78 if option == nil { 79 return nil 80 } 81 ranked := &RankedNode{ 82 Node: option, 83 } 84 return ranked 85 } 86 87 func (iter *FeasibleRankIterator) Reset() { 88 iter.source.Reset() 89 } 90 91 // StaticRankIterator is a RankIterator that returns a static set of results. 92 // This is largely only useful for testing. 93 type StaticRankIterator struct { 94 ctx Context 95 nodes []*RankedNode 96 offset int 97 seen int 98 } 99 100 // NewStaticRankIterator returns a new static rank iterator over the given nodes 101 func NewStaticRankIterator(ctx Context, nodes []*RankedNode) *StaticRankIterator { 102 iter := &StaticRankIterator{ 103 ctx: ctx, 104 nodes: nodes, 105 } 106 return iter 107 } 108 109 func (iter *StaticRankIterator) Next() *RankedNode { 110 // Check if exhausted 111 n := len(iter.nodes) 112 if iter.offset == n || iter.seen == n { 113 if iter.seen != n { 114 iter.offset = 0 115 } else { 116 return nil 117 } 118 } 119 120 // Return the next offset 121 offset := iter.offset 122 iter.offset += 1 123 iter.seen += 1 124 return iter.nodes[offset] 125 } 126 127 func (iter *StaticRankIterator) Reset() { 128 iter.seen = 0 129 } 130 131 // BinPackIterator is a RankIterator that scores potential options 132 // based on a bin-packing algorithm. 133 type BinPackIterator struct { 134 ctx Context 135 source RankIterator 136 evict bool 137 priority int 138 tasks []*structs.Task 139 } 140 141 // NewBinPackIterator returns a BinPackIterator which tries to fit tasks 142 // potentially evicting other tasks based on a given priority. 143 func NewBinPackIterator(ctx Context, source RankIterator, evict bool, priority int) *BinPackIterator { 144 iter := &BinPackIterator{ 145 ctx: ctx, 146 source: source, 147 evict: evict, 148 priority: priority, 149 } 150 return iter 151 } 152 153 func (iter *BinPackIterator) SetPriority(p int) { 154 iter.priority = p 155 } 156 157 func (iter *BinPackIterator) SetTasks(tasks []*structs.Task) { 158 iter.tasks = tasks 159 } 160 161 func (iter *BinPackIterator) Next() *RankedNode { 162 OUTER: 163 for { 164 // Get the next potential option 165 option := iter.source.Next() 166 if option == nil { 167 return nil 168 } 169 170 // Get the proposed allocations 171 proposed, err := option.ProposedAllocs(iter.ctx) 172 if err != nil { 173 iter.ctx.Logger().Printf( 174 "[ERR] sched.binpack: failed to get proposed allocations: %v", 175 err) 176 continue 177 } 178 179 // Index the existing network usage 180 netIdx := structs.NewNetworkIndex() 181 netIdx.SetNode(option.Node) 182 netIdx.AddAllocs(proposed) 183 184 // Assign the resources for each task 185 total := new(structs.Resources) 186 for _, task := range iter.tasks { 187 taskResources := task.Resources.Copy() 188 189 // Check if we need a network resource 190 if len(taskResources.Networks) > 0 { 191 ask := taskResources.Networks[0] 192 offer, err := netIdx.AssignNetwork(ask) 193 if offer == nil { 194 iter.ctx.Metrics().ExhaustedNode(option.Node, 195 fmt.Sprintf("network: %s", err)) 196 netIdx.Release() 197 continue OUTER 198 } 199 200 // Reserve this to prevent another task from colliding 201 netIdx.AddReserved(offer) 202 203 // Update the network ask to the offer 204 taskResources.Networks = []*structs.NetworkResource{offer} 205 } 206 207 // Store the task resource 208 option.SetTaskResources(task, taskResources) 209 210 // Accumulate the total resource requirement 211 total.Add(taskResources) 212 } 213 214 // Add the resources we are trying to fit 215 proposed = append(proposed, &structs.Allocation{Resources: total}) 216 217 // Check if these allocations fit, if they do not, simply skip this node 218 fit, dim, util, _ := structs.AllocsFit(option.Node, proposed, netIdx) 219 netIdx.Release() 220 if !fit { 221 iter.ctx.Metrics().ExhaustedNode(option.Node, dim) 222 continue 223 } 224 225 // XXX: For now we completely ignore evictions. We should use that flag 226 // to determine if its possible to evict other lower priority allocations 227 // to make room. This explodes the search space, so it must be done 228 // carefully. 229 230 // Score the fit normally otherwise 231 fitness := structs.ScoreFit(option.Node, util) 232 option.Score += fitness 233 iter.ctx.Metrics().ScoreNode(option.Node, "binpack", fitness) 234 return option 235 } 236 } 237 238 func (iter *BinPackIterator) Reset() { 239 iter.source.Reset() 240 } 241 242 // JobAntiAffinityIterator is used to apply an anti-affinity to allocating 243 // along side other allocations from this job. This is used to help distribute 244 // load across the cluster. 245 type JobAntiAffinityIterator struct { 246 ctx Context 247 source RankIterator 248 penalty float64 249 jobID string 250 } 251 252 // NewJobAntiAffinityIterator is used to create a JobAntiAffinityIterator that 253 // applies the given penalty for co-placement with allocs from this job. 254 func NewJobAntiAffinityIterator(ctx Context, source RankIterator, penalty float64, jobID string) *JobAntiAffinityIterator { 255 iter := &JobAntiAffinityIterator{ 256 ctx: ctx, 257 source: source, 258 penalty: penalty, 259 jobID: jobID, 260 } 261 return iter 262 } 263 264 func (iter *JobAntiAffinityIterator) SetJob(jobID string) { 265 iter.jobID = jobID 266 } 267 268 func (iter *JobAntiAffinityIterator) Next() *RankedNode { 269 for { 270 option := iter.source.Next() 271 if option == nil { 272 return nil 273 } 274 275 // Get the proposed allocations 276 proposed, err := option.ProposedAllocs(iter.ctx) 277 if err != nil { 278 iter.ctx.Logger().Printf( 279 "[ERR] sched.job-anti-aff: failed to get proposed allocations: %v", 280 err) 281 continue 282 } 283 284 // Determine the number of collisions 285 collisions := 0 286 for _, alloc := range proposed { 287 if alloc.JobID == iter.jobID { 288 collisions += 1 289 } 290 } 291 292 // Apply a penalty if there are collisions 293 if collisions > 0 { 294 scorePenalty := -1 * float64(collisions) * iter.penalty 295 option.Score += scorePenalty 296 iter.ctx.Metrics().ScoreNode(option.Node, "job-anti-affinity", scorePenalty) 297 } 298 return option 299 } 300 } 301 302 func (iter *JobAntiAffinityIterator) Reset() { 303 iter.source.Reset() 304 }