github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/scheduler/testing.go (about)

     1  package scheduler
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"sync"
     8  	"testing"
     9  
    10  	"github.com/hashicorp/nomad/nomad/state"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  )
    13  
    14  // RejectPlan is used to always reject the entire plan and force a state refresh
    15  type RejectPlan struct {
    16  	Harness *Harness
    17  }
    18  
    19  func (r *RejectPlan) SubmitPlan(*structs.Plan) (*structs.PlanResult, State, error) {
    20  	result := new(structs.PlanResult)
    21  	result.RefreshIndex = r.Harness.NextIndex()
    22  	return result, r.Harness.State, nil
    23  }
    24  
    25  func (r *RejectPlan) UpdateEval(eval *structs.Evaluation) error {
    26  	return nil
    27  }
    28  
    29  func (r *RejectPlan) CreateEval(*structs.Evaluation) error {
    30  	return nil
    31  }
    32  
    33  func (r *RejectPlan) ReblockEval(*structs.Evaluation) error {
    34  	return nil
    35  }
    36  
    37  // Harness is a lightweight testing harness for schedulers. It manages a state
    38  // store copy and provides the planner interface. It can be extended for various
    39  // testing uses or for invoking the scheduler without side effects.
    40  type Harness struct {
    41  	State *state.StateStore
    42  
    43  	Planner  Planner
    44  	planLock sync.Mutex
    45  
    46  	Plans        []*structs.Plan
    47  	Evals        []*structs.Evaluation
    48  	CreateEvals  []*structs.Evaluation
    49  	ReblockEvals []*structs.Evaluation
    50  
    51  	nextIndex     uint64
    52  	nextIndexLock sync.Mutex
    53  }
    54  
    55  // NewHarness is used to make a new testing harness
    56  func NewHarness(t *testing.T) *Harness {
    57  	state, err := state.NewStateStore(os.Stderr)
    58  	if err != nil {
    59  		t.Fatalf("err: %v", err)
    60  	}
    61  
    62  	h := &Harness{
    63  		State:     state,
    64  		nextIndex: 1,
    65  	}
    66  	return h
    67  }
    68  
    69  // SubmitPlan is used to handle plan submission
    70  func (h *Harness) SubmitPlan(plan *structs.Plan) (*structs.PlanResult, State, error) {
    71  	// Ensure sequential plan application
    72  	h.planLock.Lock()
    73  	defer h.planLock.Unlock()
    74  
    75  	// Store the plan
    76  	h.Plans = append(h.Plans, plan)
    77  
    78  	// Check for custom planner
    79  	if h.Planner != nil {
    80  		return h.Planner.SubmitPlan(plan)
    81  	}
    82  
    83  	// Get the index
    84  	index := h.NextIndex()
    85  
    86  	// Prepare the result
    87  	result := new(structs.PlanResult)
    88  	result.NodeUpdate = plan.NodeUpdate
    89  	result.NodeAllocation = plan.NodeAllocation
    90  	result.AllocIndex = index
    91  
    92  	// Flatten evicts and allocs
    93  	var allocs []*structs.Allocation
    94  	for _, updateList := range plan.NodeUpdate {
    95  		allocs = append(allocs, updateList...)
    96  	}
    97  	for _, allocList := range plan.NodeAllocation {
    98  		allocs = append(allocs, allocList...)
    99  	}
   100  
   101  	// Attach the plan to all the allocations. It is pulled out in the
   102  	// payload to avoid the redundancy of encoding, but should be denormalized
   103  	// prior to being inserted into MemDB.
   104  	if j := plan.Job; j != nil {
   105  		for _, alloc := range allocs {
   106  			if alloc.Job == nil {
   107  				alloc.Job = j
   108  			}
   109  		}
   110  	}
   111  
   112  	// Apply the full plan
   113  	err := h.State.UpsertAllocs(index, allocs)
   114  	return result, nil, err
   115  }
   116  
   117  func (h *Harness) UpdateEval(eval *structs.Evaluation) error {
   118  	// Ensure sequential plan application
   119  	h.planLock.Lock()
   120  	defer h.planLock.Unlock()
   121  
   122  	// Store the eval
   123  	h.Evals = append(h.Evals, eval)
   124  
   125  	// Check for custom planner
   126  	if h.Planner != nil {
   127  		return h.Planner.UpdateEval(eval)
   128  	}
   129  	return nil
   130  }
   131  
   132  func (h *Harness) CreateEval(eval *structs.Evaluation) error {
   133  	// Ensure sequential plan application
   134  	h.planLock.Lock()
   135  	defer h.planLock.Unlock()
   136  
   137  	// Store the eval
   138  	h.CreateEvals = append(h.CreateEvals, eval)
   139  
   140  	// Check for custom planner
   141  	if h.Planner != nil {
   142  		return h.Planner.CreateEval(eval)
   143  	}
   144  	return nil
   145  }
   146  
   147  func (h *Harness) ReblockEval(eval *structs.Evaluation) error {
   148  	// Ensure sequential plan application
   149  	h.planLock.Lock()
   150  	defer h.planLock.Unlock()
   151  
   152  	// Check that the evaluation was already blocked.
   153  	old, err := h.State.EvalByID(eval.ID)
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	if old == nil {
   159  		return fmt.Errorf("evaluation does not exist to be reblocked")
   160  	}
   161  	if old.Status != structs.EvalStatusBlocked {
   162  		return fmt.Errorf("evaluation %q is not already in a blocked state", old.ID)
   163  	}
   164  
   165  	h.ReblockEvals = append(h.ReblockEvals, eval)
   166  	return nil
   167  }
   168  
   169  // NextIndex returns the next index
   170  func (h *Harness) NextIndex() uint64 {
   171  	h.nextIndexLock.Lock()
   172  	defer h.nextIndexLock.Unlock()
   173  	idx := h.nextIndex
   174  	h.nextIndex += 1
   175  	return idx
   176  }
   177  
   178  // Snapshot is used to snapshot the current state
   179  func (h *Harness) Snapshot() State {
   180  	snap, _ := h.State.Snapshot()
   181  	return snap
   182  }
   183  
   184  // Scheduler is used to return a new scheduler from
   185  // a snapshot of current state using the harness for planning.
   186  func (h *Harness) Scheduler(factory Factory) Scheduler {
   187  	logger := log.New(os.Stderr, "", log.LstdFlags)
   188  	return factory(logger, h.Snapshot(), h)
   189  }
   190  
   191  // Process is used to process an evaluation given a factory
   192  // function to create the scheduler
   193  func (h *Harness) Process(factory Factory, eval *structs.Evaluation) error {
   194  	sched := h.Scheduler(factory)
   195  	return sched.Process(eval)
   196  }
   197  
   198  func (h *Harness) AssertEvalStatus(t *testing.T, state string) {
   199  	if len(h.Evals) != 1 {
   200  		t.Fatalf("bad: %#v", h.Evals)
   201  	}
   202  	update := h.Evals[0]
   203  
   204  	if update.Status != state {
   205  		t.Fatalf("bad: %#v", update)
   206  	}
   207  }