mirror of
https://github.com/kemko/nomad.git
synced 2026-01-06 18:35:44 +03:00
scheduler: testing utility methods
This commit is contained in:
@@ -106,7 +106,9 @@ func Job() *structs.Job {
|
||||
Meta: map[string]string{
|
||||
"owner": "armon",
|
||||
},
|
||||
Status: structs.JobStatusPending,
|
||||
Status: structs.JobStatusPending,
|
||||
CreateIndex: 42,
|
||||
ModifyIndex: 99,
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func (s *ServiceScheduler) Process(eval *structs.Evaluation) error {
|
||||
case structs.EvalTriggerJobRegister:
|
||||
return s.handleJobRegister(eval)
|
||||
case structs.EvalTriggerJobDeregister:
|
||||
return s.handleJobDeregister(eval)
|
||||
return s.evictJobAllocs(eval)
|
||||
case structs.EvalTriggerNodeUpdate:
|
||||
return s.handleNodeUpdate(eval)
|
||||
default:
|
||||
@@ -80,7 +80,7 @@ START:
|
||||
|
||||
// If there is nothing required for this job, treat like a deregister
|
||||
if len(groups) == 0 {
|
||||
return s.handleJobDeregister(eval)
|
||||
return s.evictJobAllocs(eval)
|
||||
}
|
||||
|
||||
// Lookup the allocations by JobID
|
||||
@@ -292,8 +292,15 @@ func (s *ServiceScheduler) planAllocations(stack *IteratorStack, job *structs.Jo
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleJobDeregister is used to handle a job being deregistered
|
||||
func (s *ServiceScheduler) handleJobDeregister(eval *structs.Evaluation) error {
|
||||
// handleNodeUpdate is used to handle an update to a node status where
|
||||
// there is an existing allocation for this job
|
||||
func (s *ServiceScheduler) handleNodeUpdate(eval *structs.Evaluation) error {
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
// evictJobAllocs is used to evict all job allocations
|
||||
func (s *ServiceScheduler) evictJobAllocs(eval *structs.Evaluation) error {
|
||||
START:
|
||||
// Lookup the allocations by JobID
|
||||
allocs, err := s.state.AllocsByJob(eval.JobID)
|
||||
@@ -335,103 +342,3 @@ START:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleNodeUpdate is used to handle an update to a node status where
|
||||
// there is an existing allocation for this job
|
||||
func (s *ServiceScheduler) handleNodeUpdate(eval *structs.Evaluation) error {
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
// materializeTaskGroups is used to materialize all the task groups
|
||||
// a job requires. This is used to do the count expansion.
|
||||
func materializeTaskGroups(job *structs.Job) map[string]*structs.TaskGroup {
|
||||
out := make(map[string]*structs.TaskGroup)
|
||||
for _, tg := range job.TaskGroups {
|
||||
for i := 0; i < tg.Count; i++ {
|
||||
name := fmt.Sprintf("%s.%s[%d]", job.Name, tg.Name, i)
|
||||
out[name] = tg
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// indexAllocs is used to index a list of allocations by name
|
||||
func indexAllocs(allocs []*structs.Allocation) map[string][]*structs.Allocation {
|
||||
out := make(map[string][]*structs.Allocation)
|
||||
for _, alloc := range allocs {
|
||||
name := alloc.Name
|
||||
out[name] = append(out[name], alloc)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// allocNameID is a tuple of the allocation name and ID
|
||||
type allocNameID struct {
|
||||
Name string
|
||||
ID string
|
||||
}
|
||||
|
||||
// diffAllocs is used to do a set difference between the target allocations
|
||||
// and the existing allocations. This returns 4 sets of results, the list of
|
||||
// named task groups that need to be placed (no existing allocation), the
|
||||
// allocations that need to be updated (job definition is newer), the allocs
|
||||
// that need to be evicted (no longer required), and those that should be
|
||||
// ignored.
|
||||
func diffAllocs(job *structs.Job,
|
||||
required map[string]*structs.TaskGroup,
|
||||
existing map[string][]*structs.Allocation) (place, update, evict, ignore []allocNameID) {
|
||||
// Scan the existing updates
|
||||
for name, existList := range existing {
|
||||
for _, exist := range existList {
|
||||
// Check for the definition in the required set
|
||||
_, ok := required[name]
|
||||
|
||||
// If not required, we evict
|
||||
if !ok {
|
||||
evict = append(evict, allocNameID{name, exist.ID})
|
||||
continue
|
||||
}
|
||||
|
||||
// If the definition is updated we need to update
|
||||
// XXX: This is an extremely conservative approach. We can check
|
||||
// if the job definition has changed in a way that affects
|
||||
// this allocation and potentially ignore it.
|
||||
if job.ModifyIndex != exist.Job.ModifyIndex {
|
||||
update = append(update, allocNameID{name, exist.ID})
|
||||
continue
|
||||
}
|
||||
|
||||
// Everything is up-to-date
|
||||
ignore = append(ignore, allocNameID{name, exist.ID})
|
||||
}
|
||||
}
|
||||
|
||||
// Scan the required groups
|
||||
for name := range required {
|
||||
// Check for an existing allocation
|
||||
_, ok := existing[name]
|
||||
|
||||
// Require a placement if no existing allocation. If there
|
||||
// is an existing allocation, we would have checked for a potential
|
||||
// update or ignore above.
|
||||
if !ok {
|
||||
place = append(place, allocNameID{name, ""})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// addEvictsToPlan is used to add all the evictions to the plan
|
||||
func addEvictsToPlan(plan *structs.Plan,
|
||||
evicts []allocNameID, indexed map[string][]*structs.Allocation) {
|
||||
for _, evict := range evicts {
|
||||
list := indexed[evict.Name]
|
||||
for _, alloc := range list {
|
||||
if alloc.ID != evict.ID {
|
||||
continue
|
||||
}
|
||||
plan.AppendEvict(alloc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
99
scheduler/util.go
Normal file
99
scheduler/util.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
)
|
||||
|
||||
// allocNameID is a tuple of the allocation name and potential alloc ID
|
||||
type allocNameID struct {
|
||||
Name string
|
||||
ID string
|
||||
}
|
||||
|
||||
// materializeTaskGroups is used to materialize all the task groups
|
||||
// a job requires. This is used to do the count expansion.
|
||||
func materializeTaskGroups(job *structs.Job) map[string]*structs.TaskGroup {
|
||||
out := make(map[string]*structs.TaskGroup)
|
||||
for _, tg := range job.TaskGroups {
|
||||
for i := 0; i < tg.Count; i++ {
|
||||
name := fmt.Sprintf("%s.%s[%d]", job.Name, tg.Name, i)
|
||||
out[name] = tg
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// indexAllocs is used to index a list of allocations by name
|
||||
func indexAllocs(allocs []*structs.Allocation) map[string][]*structs.Allocation {
|
||||
out := make(map[string][]*structs.Allocation)
|
||||
for _, alloc := range allocs {
|
||||
name := alloc.Name
|
||||
out[name] = append(out[name], alloc)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// diffAllocs is used to do a set difference between the target allocations
|
||||
// and the existing allocations. This returns 4 sets of results, the list of
|
||||
// named task groups that need to be placed (no existing allocation), the
|
||||
// allocations that need to be updated (job definition is newer), the allocs
|
||||
// that need to be evicted (no longer required), and those that should be
|
||||
// ignored.
|
||||
func diffAllocs(job *structs.Job,
|
||||
required map[string]*structs.TaskGroup,
|
||||
existing map[string][]*structs.Allocation) (place, update, evict, ignore []allocNameID) {
|
||||
// Scan the existing updates
|
||||
for name, existList := range existing {
|
||||
for _, exist := range existList {
|
||||
// Check for the definition in the required set
|
||||
_, ok := required[name]
|
||||
|
||||
// If not required, we evict
|
||||
if !ok {
|
||||
evict = append(evict, allocNameID{name, exist.ID})
|
||||
continue
|
||||
}
|
||||
|
||||
// If the definition is updated we need to update
|
||||
// XXX: This is an extremely conservative approach. We can check
|
||||
// if the job definition has changed in a way that affects
|
||||
// this allocation and potentially ignore it.
|
||||
if job.ModifyIndex != exist.Job.ModifyIndex {
|
||||
update = append(update, allocNameID{name, exist.ID})
|
||||
continue
|
||||
}
|
||||
|
||||
// Everything is up-to-date
|
||||
ignore = append(ignore, allocNameID{name, exist.ID})
|
||||
}
|
||||
}
|
||||
|
||||
// Scan the required groups
|
||||
for name := range required {
|
||||
// Check for an existing allocation
|
||||
_, ok := existing[name]
|
||||
|
||||
// Require a placement if no existing allocation. If there
|
||||
// is an existing allocation, we would have checked for a potential
|
||||
// update or ignore above.
|
||||
if !ok {
|
||||
place = append(place, allocNameID{name, ""})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// addEvictsToPlan is used to add all the evictions to the plan
|
||||
func addEvictsToPlan(plan *structs.Plan,
|
||||
evicts []allocNameID, indexed map[string][]*structs.Allocation) {
|
||||
for _, evict := range evicts {
|
||||
list := indexed[evict.Name]
|
||||
for _, alloc := range list {
|
||||
if alloc.ID == evict.ID {
|
||||
plan.AppendEvict(alloc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
142
scheduler/util_test.go
Normal file
142
scheduler/util_test.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/nomad/mock"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
)
|
||||
|
||||
func TestMaterializeTaskGroups(t *testing.T) {
|
||||
job := mock.Job()
|
||||
index := materializeTaskGroups(job)
|
||||
if len(index) != 10 {
|
||||
t.Fatalf("Bad: %#v", index)
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
name := fmt.Sprintf("my-job.web[%d]", i)
|
||||
tg, ok := index[name]
|
||||
if !ok {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
if tg != job.TaskGroups[0] {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexAllocs(t *testing.T) {
|
||||
allocs := []*structs.Allocation{
|
||||
&structs.Allocation{Name: "foo"},
|
||||
&structs.Allocation{Name: "foo"},
|
||||
&structs.Allocation{Name: "bar"},
|
||||
}
|
||||
index := indexAllocs(allocs)
|
||||
if len(index) != 2 {
|
||||
t.Fatalf("bad: %#v", index)
|
||||
}
|
||||
if len(index["foo"]) != 2 {
|
||||
t.Fatalf("bad: %#v", index)
|
||||
}
|
||||
if len(index["bar"]) != 1 {
|
||||
t.Fatalf("bad: %#v", index)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffAllocs(t *testing.T) {
|
||||
job := mock.Job()
|
||||
required := materializeTaskGroups(job)
|
||||
|
||||
// The "old" job has a previous modify index
|
||||
oldJob := new(structs.Job)
|
||||
*oldJob = *job
|
||||
oldJob.ModifyIndex -= 1
|
||||
|
||||
allocs := []*structs.Allocation{
|
||||
// Update the 1st
|
||||
&structs.Allocation{
|
||||
ID: mock.GenerateUUID(),
|
||||
NodeID: "zip",
|
||||
Name: "my-job.web[0]",
|
||||
Job: oldJob,
|
||||
},
|
||||
|
||||
// Ignore the 2rd
|
||||
&structs.Allocation{
|
||||
ID: mock.GenerateUUID(),
|
||||
NodeID: "zip",
|
||||
Name: "my-job.web[1]",
|
||||
Job: job,
|
||||
},
|
||||
|
||||
// Evict 11th
|
||||
&structs.Allocation{
|
||||
ID: mock.GenerateUUID(),
|
||||
NodeID: "zip",
|
||||
Name: "my-job.web[10]",
|
||||
},
|
||||
}
|
||||
existing := indexAllocs(allocs)
|
||||
|
||||
place, update, evict, ignore := diffAllocs(job, required, existing)
|
||||
|
||||
// We should update the first alloc
|
||||
if len(update) != 1 || update[0].ID != allocs[0].ID {
|
||||
t.Fatalf("bad: %#v", update)
|
||||
}
|
||||
|
||||
// We should ignore the second alloc
|
||||
if len(ignore) != 1 || ignore[0].ID != allocs[1].ID {
|
||||
t.Fatalf("bad: %#v", ignore)
|
||||
}
|
||||
|
||||
// We should evict the 3rd alloc
|
||||
if len(evict) != 1 || evict[0].ID != allocs[2].ID {
|
||||
t.Fatalf("bad: %#v", evict)
|
||||
}
|
||||
|
||||
// We should place 8
|
||||
if len(place) != 8 {
|
||||
t.Fatalf("bad: %#v", place)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddEvictsToPlan(t *testing.T) {
|
||||
allocs := []*structs.Allocation{
|
||||
&structs.Allocation{
|
||||
ID: mock.GenerateUUID(),
|
||||
NodeID: "zip",
|
||||
Name: "foo",
|
||||
},
|
||||
&structs.Allocation{
|
||||
ID: mock.GenerateUUID(),
|
||||
NodeID: "zip",
|
||||
Name: "foo",
|
||||
},
|
||||
&structs.Allocation{
|
||||
ID: mock.GenerateUUID(),
|
||||
NodeID: "zip",
|
||||
Name: "bar",
|
||||
},
|
||||
}
|
||||
plan := &structs.Plan{
|
||||
NodeEvict: make(map[string][]string),
|
||||
}
|
||||
index := indexAllocs(allocs)
|
||||
|
||||
evict := []allocNameID{
|
||||
allocNameID{Name: "foo", ID: allocs[0].ID},
|
||||
allocNameID{Name: "bar", ID: allocs[2].ID},
|
||||
}
|
||||
addEvictsToPlan(plan, evict, index)
|
||||
|
||||
nodeEvict := plan.NodeEvict["zip"]
|
||||
if len(nodeEvict) != 2 {
|
||||
t.Fatalf("bad: %#v %v", plan, nodeEvict)
|
||||
}
|
||||
if nodeEvict[0] != allocs[0].ID || nodeEvict[1] != allocs[2].ID {
|
||||
t.Fatalf("bad: %v", nodeEvict)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user