github.com/hspak/nomad@v0.7.2-0.20180309000617-bc4ae22a39a5/scheduler/testing.go (about)

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