github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/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  // NewHarnessWithState creates a new harness with the given state for testing
    70  // purposes.
    71  func NewHarnessWithState(t *testing.T, state *state.StateStore) *Harness {
    72  	return &Harness{
    73  		State:     state,
    74  		nextIndex: 1,
    75  	}
    76  }
    77  
    78  // SubmitPlan is used to handle plan submission
    79  func (h *Harness) SubmitPlan(plan *structs.Plan) (*structs.PlanResult, State, error) {
    80  	// Ensure sequential plan application
    81  	h.planLock.Lock()
    82  	defer h.planLock.Unlock()
    83  
    84  	// Store the plan
    85  	h.Plans = append(h.Plans, plan)
    86  
    87  	// Check for custom planner
    88  	if h.Planner != nil {
    89  		return h.Planner.SubmitPlan(plan)
    90  	}
    91  
    92  	// Get the index
    93  	index := h.NextIndex()
    94  
    95  	// Prepare the result
    96  	result := new(structs.PlanResult)
    97  	result.NodeUpdate = plan.NodeUpdate
    98  	result.NodeAllocation = plan.NodeAllocation
    99  	result.AllocIndex = index
   100  
   101  	// Flatten evicts and allocs
   102  	var allocs []*structs.Allocation
   103  	for _, updateList := range plan.NodeUpdate {
   104  		allocs = append(allocs, updateList...)
   105  	}
   106  	for _, allocList := range plan.NodeAllocation {
   107  		allocs = append(allocs, allocList...)
   108  	}
   109  
   110  	// Attach the plan to all the allocations. It is pulled out in the
   111  	// payload to avoid the redundancy of encoding, but should be denormalized
   112  	// prior to being inserted into MemDB.
   113  	if j := plan.Job; j != nil {
   114  		for _, alloc := range allocs {
   115  			if alloc.Job == nil {
   116  				alloc.Job = j
   117  			}
   118  		}
   119  	}
   120  
   121  	// Apply the full plan
   122  	err := h.State.UpsertAllocs(index, allocs)
   123  	return result, nil, err
   124  }
   125  
   126  func (h *Harness) UpdateEval(eval *structs.Evaluation) error {
   127  	// Ensure sequential plan application
   128  	h.planLock.Lock()
   129  	defer h.planLock.Unlock()
   130  
   131  	// Store the eval
   132  	h.Evals = append(h.Evals, eval)
   133  
   134  	// Check for custom planner
   135  	if h.Planner != nil {
   136  		return h.Planner.UpdateEval(eval)
   137  	}
   138  	return nil
   139  }
   140  
   141  func (h *Harness) CreateEval(eval *structs.Evaluation) error {
   142  	// Ensure sequential plan application
   143  	h.planLock.Lock()
   144  	defer h.planLock.Unlock()
   145  
   146  	// Store the eval
   147  	h.CreateEvals = append(h.CreateEvals, eval)
   148  
   149  	// Check for custom planner
   150  	if h.Planner != nil {
   151  		return h.Planner.CreateEval(eval)
   152  	}
   153  	return nil
   154  }
   155  
   156  func (h *Harness) ReblockEval(eval *structs.Evaluation) error {
   157  	// Ensure sequential plan application
   158  	h.planLock.Lock()
   159  	defer h.planLock.Unlock()
   160  
   161  	// Check that the evaluation was already blocked.
   162  	old, err := h.State.EvalByID(eval.ID)
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	if old == nil {
   168  		return fmt.Errorf("evaluation does not exist to be reblocked")
   169  	}
   170  	if old.Status != structs.EvalStatusBlocked {
   171  		return fmt.Errorf("evaluation %q is not already in a blocked state", old.ID)
   172  	}
   173  
   174  	h.ReblockEvals = append(h.ReblockEvals, eval)
   175  	return nil
   176  }
   177  
   178  // NextIndex returns the next index
   179  func (h *Harness) NextIndex() uint64 {
   180  	h.nextIndexLock.Lock()
   181  	defer h.nextIndexLock.Unlock()
   182  	idx := h.nextIndex
   183  	h.nextIndex += 1
   184  	return idx
   185  }
   186  
   187  // Snapshot is used to snapshot the current state
   188  func (h *Harness) Snapshot() State {
   189  	snap, _ := h.State.Snapshot()
   190  	return snap
   191  }
   192  
   193  // Scheduler is used to return a new scheduler from
   194  // a snapshot of current state using the harness for planning.
   195  func (h *Harness) Scheduler(factory Factory) Scheduler {
   196  	logger := log.New(os.Stderr, "", log.LstdFlags)
   197  	return factory(logger, h.Snapshot(), h)
   198  }
   199  
   200  // Process is used to process an evaluation given a factory
   201  // function to create the scheduler
   202  func (h *Harness) Process(factory Factory, eval *structs.Evaluation) error {
   203  	sched := h.Scheduler(factory)
   204  	return sched.Process(eval)
   205  }
   206  
   207  func (h *Harness) AssertEvalStatus(t *testing.T, state string) {
   208  	if len(h.Evals) != 1 {
   209  		t.Fatalf("bad: %#v", h.Evals)
   210  	}
   211  	update := h.Evals[0]
   212  
   213  	if update.Status != state {
   214  		t.Fatalf("bad: %#v", update)
   215  	}
   216  }