github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/scheduler/testing.go (about)

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