github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/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 continue OUTER 197 } 198 199 // Reserve this to prevent another task from colliding 200 netIdx.AddReserved(offer) 201 202 // Update the network ask to the offer 203 taskResources.Networks = []*structs.NetworkResource{offer} 204 } 205 206 // Store the task resource 207 option.SetTaskResources(task, taskResources) 208 209 // Accumulate the total resource requirement 210 total.Add(taskResources) 211 } 212 213 // Add the resources we are trying to fit 214 proposed = append(proposed, &structs.Allocation{Resources: total}) 215 216 // Check if these allocations fit, if they do not, simply skip this node 217 fit, dim, util, _ := structs.AllocsFit(option.Node, proposed, netIdx) 218 if !fit { 219 iter.ctx.Metrics().ExhaustedNode(option.Node, dim) 220 continue 221 } 222 223 // XXX: For now we completely ignore evictions. We should use that flag 224 // to determine if its possible to evict other lower priority allocations 225 // to make room. This explodes the search space, so it must be done 226 // carefully. 227 228 // Score the fit normally otherwise 229 fitness := structs.ScoreFit(option.Node, util) 230 option.Score += fitness 231 iter.ctx.Metrics().ScoreNode(option.Node, "binpack", fitness) 232 return option 233 } 234 } 235 236 func (iter *BinPackIterator) Reset() { 237 iter.source.Reset() 238 } 239 240 // JobAntiAffinityIterator is used to apply an anti-affinity to allocating 241 // along side other allocations from this job. This is used to help distribute 242 // load across the cluster. 243 type JobAntiAffinityIterator struct { 244 ctx Context 245 source RankIterator 246 penalty float64 247 jobID string 248 } 249 250 // NewJobAntiAffinityIterator is used to create a JobAntiAffinityIterator that 251 // applies the given penalty for co-placement with allocs from this job. 252 func NewJobAntiAffinityIterator(ctx Context, source RankIterator, penalty float64, jobID string) *JobAntiAffinityIterator { 253 iter := &JobAntiAffinityIterator{ 254 ctx: ctx, 255 source: source, 256 penalty: penalty, 257 jobID: jobID, 258 } 259 return iter 260 } 261 262 func (iter *JobAntiAffinityIterator) SetJob(jobID string) { 263 iter.jobID = jobID 264 } 265 266 func (iter *JobAntiAffinityIterator) Next() *RankedNode { 267 for { 268 option := iter.source.Next() 269 if option == nil { 270 return nil 271 } 272 273 // Get the proposed allocations 274 proposed, err := option.ProposedAllocs(iter.ctx) 275 if err != nil { 276 iter.ctx.Logger().Printf( 277 "[ERR] sched.job-anti-aff: failed to get proposed allocations: %v", 278 err) 279 continue 280 } 281 282 // Determine the number of collisions 283 collisions := 0 284 for _, alloc := range proposed { 285 if alloc.JobID == iter.jobID { 286 collisions += 1 287 } 288 } 289 290 // Apply a penalty if there are collisions 291 if collisions > 0 { 292 scorePenalty := -1 * float64(collisions) * iter.penalty 293 option.Score += scorePenalty 294 iter.ctx.Metrics().ScoreNode(option.Node, "job-anti-affinity", scorePenalty) 295 } 296 return option 297 } 298 } 299 300 func (iter *JobAntiAffinityIterator) Reset() { 301 iter.source.Reset() 302 }