github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/nomad/job_endpoint_test.go (about)

     1  package nomad
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/hashicorp/net-rpc-msgpackrpc"
     9  	"github.com/hashicorp/nomad/nomad/mock"
    10  	"github.com/hashicorp/nomad/nomad/structs"
    11  	"github.com/hashicorp/nomad/testutil"
    12  )
    13  
    14  func TestJobEndpoint_Register(t *testing.T) {
    15  	s1 := testServer(t, func(c *Config) {
    16  		c.NumSchedulers = 0 // Prevent automatic dequeue
    17  	})
    18  	defer s1.Shutdown()
    19  	codec := rpcClient(t, s1)
    20  	testutil.WaitForLeader(t, s1.RPC)
    21  
    22  	// Create the register request
    23  	job := mock.Job()
    24  	req := &structs.JobRegisterRequest{
    25  		Job:          job,
    26  		WriteRequest: structs.WriteRequest{Region: "global"},
    27  	}
    28  
    29  	// Fetch the response
    30  	var resp structs.JobRegisterResponse
    31  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
    32  		t.Fatalf("err: %v", err)
    33  	}
    34  	if resp.Index == 0 {
    35  		t.Fatalf("bad index: %d", resp.Index)
    36  	}
    37  
    38  	// Check for the node in the FSM
    39  	state := s1.fsm.State()
    40  	out, err := state.JobByID(job.ID)
    41  	if err != nil {
    42  		t.Fatalf("err: %v", err)
    43  	}
    44  	if out == nil {
    45  		t.Fatalf("expected job")
    46  	}
    47  	if out.CreateIndex != resp.JobModifyIndex {
    48  		t.Fatalf("index mis-match")
    49  	}
    50  	serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name
    51  	expectedServiceName := "web-frontend"
    52  	if serviceName != expectedServiceName {
    53  		t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName)
    54  	}
    55  
    56  	// Lookup the evaluation
    57  	eval, err := state.EvalByID(resp.EvalID)
    58  	if err != nil {
    59  		t.Fatalf("err: %v", err)
    60  	}
    61  	if eval == nil {
    62  		t.Fatalf("expected eval")
    63  	}
    64  	if eval.CreateIndex != resp.EvalCreateIndex {
    65  		t.Fatalf("index mis-match")
    66  	}
    67  
    68  	if eval.Priority != job.Priority {
    69  		t.Fatalf("bad: %#v", eval)
    70  	}
    71  	if eval.Type != job.Type {
    72  		t.Fatalf("bad: %#v", eval)
    73  	}
    74  	if eval.TriggeredBy != structs.EvalTriggerJobRegister {
    75  		t.Fatalf("bad: %#v", eval)
    76  	}
    77  	if eval.JobID != job.ID {
    78  		t.Fatalf("bad: %#v", eval)
    79  	}
    80  	if eval.JobModifyIndex != resp.JobModifyIndex {
    81  		t.Fatalf("bad: %#v", eval)
    82  	}
    83  	if eval.Status != structs.EvalStatusPending {
    84  		t.Fatalf("bad: %#v", eval)
    85  	}
    86  }
    87  
    88  func TestJobEndpoint_Register_Existing(t *testing.T) {
    89  	s1 := testServer(t, func(c *Config) {
    90  		c.NumSchedulers = 0 // Prevent automatic dequeue
    91  	})
    92  	defer s1.Shutdown()
    93  	codec := rpcClient(t, s1)
    94  	testutil.WaitForLeader(t, s1.RPC)
    95  
    96  	// Create the register request
    97  	job := mock.Job()
    98  	req := &structs.JobRegisterRequest{
    99  		Job:          job,
   100  		WriteRequest: structs.WriteRequest{Region: "global"},
   101  	}
   102  
   103  	// Fetch the response
   104  	var resp structs.JobRegisterResponse
   105  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   106  		t.Fatalf("err: %v", err)
   107  	}
   108  	if resp.Index == 0 {
   109  		t.Fatalf("bad index: %d", resp.Index)
   110  	}
   111  
   112  	// Update the job definition
   113  	job2 := mock.Job()
   114  	job2.Priority = 100
   115  	job2.ID = job.ID
   116  	req.Job = job2
   117  
   118  	// Attempt update
   119  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   120  		t.Fatalf("err: %v", err)
   121  	}
   122  	if resp.Index == 0 {
   123  		t.Fatalf("bad index: %d", resp.Index)
   124  	}
   125  
   126  	// Check for the node in the FSM
   127  	state := s1.fsm.State()
   128  	out, err := state.JobByID(job.ID)
   129  	if err != nil {
   130  		t.Fatalf("err: %v", err)
   131  	}
   132  	if out == nil {
   133  		t.Fatalf("expected job")
   134  	}
   135  	if out.ModifyIndex != resp.JobModifyIndex {
   136  		t.Fatalf("index mis-match")
   137  	}
   138  	if out.Priority != 100 {
   139  		t.Fatalf("expected update")
   140  	}
   141  
   142  	// Lookup the evaluation
   143  	eval, err := state.EvalByID(resp.EvalID)
   144  	if err != nil {
   145  		t.Fatalf("err: %v", err)
   146  	}
   147  	if eval == nil {
   148  		t.Fatalf("expected eval")
   149  	}
   150  	if eval.CreateIndex != resp.EvalCreateIndex {
   151  		t.Fatalf("index mis-match")
   152  	}
   153  
   154  	if eval.Priority != job2.Priority {
   155  		t.Fatalf("bad: %#v", eval)
   156  	}
   157  	if eval.Type != job2.Type {
   158  		t.Fatalf("bad: %#v", eval)
   159  	}
   160  	if eval.TriggeredBy != structs.EvalTriggerJobRegister {
   161  		t.Fatalf("bad: %#v", eval)
   162  	}
   163  	if eval.JobID != job2.ID {
   164  		t.Fatalf("bad: %#v", eval)
   165  	}
   166  	if eval.JobModifyIndex != resp.JobModifyIndex {
   167  		t.Fatalf("bad: %#v", eval)
   168  	}
   169  	if eval.Status != structs.EvalStatusPending {
   170  		t.Fatalf("bad: %#v", eval)
   171  	}
   172  }
   173  
   174  func TestJobEndpoint_Register_Batch(t *testing.T) {
   175  	s1 := testServer(t, func(c *Config) {
   176  		c.NumSchedulers = 0 // Prevent automatic dequeue
   177  	})
   178  	defer s1.Shutdown()
   179  	codec := rpcClient(t, s1)
   180  	testutil.WaitForLeader(t, s1.RPC)
   181  
   182  	// Create the register request
   183  	job := mock.Job()
   184  	job.Type = structs.JobTypeBatch
   185  	req := &structs.JobRegisterRequest{
   186  		Job:          job,
   187  		WriteRequest: structs.WriteRequest{Region: "global"},
   188  	}
   189  
   190  	// Fetch the response
   191  	var resp structs.JobRegisterResponse
   192  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   193  		t.Fatalf("err: %v", err)
   194  	}
   195  	if resp.Index == 0 {
   196  		t.Fatalf("bad index: %d", resp.Index)
   197  	}
   198  
   199  	// Check for the node in the FSM
   200  	state := s1.fsm.State()
   201  	out, err := state.JobByID(job.ID)
   202  	if err != nil {
   203  		t.Fatalf("err: %v", err)
   204  	}
   205  	if out == nil {
   206  		t.Fatalf("expected job")
   207  	}
   208  	if !out.GC {
   209  		t.Fatal("expect batch job to be made garbage collectible")
   210  	}
   211  }
   212  
   213  func TestJobEndpoint_Register_GC_Set(t *testing.T) {
   214  	s1 := testServer(t, func(c *Config) {
   215  		c.NumSchedulers = 0 // Prevent automatic dequeue
   216  	})
   217  	defer s1.Shutdown()
   218  	codec := rpcClient(t, s1)
   219  	testutil.WaitForLeader(t, s1.RPC)
   220  
   221  	// Create the register request
   222  	job := mock.Job()
   223  	job.GC = true
   224  	req := &structs.JobRegisterRequest{
   225  		Job:          job,
   226  		WriteRequest: structs.WriteRequest{Region: "global"},
   227  	}
   228  
   229  	// Fetch the response
   230  	var resp structs.JobRegisterResponse
   231  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err == nil {
   232  		t.Fatalf("expect err")
   233  	}
   234  }
   235  
   236  func TestJobEndpoint_Register_Periodic(t *testing.T) {
   237  	s1 := testServer(t, func(c *Config) {
   238  		c.NumSchedulers = 0 // Prevent automatic dequeue
   239  	})
   240  	defer s1.Shutdown()
   241  	codec := rpcClient(t, s1)
   242  	testutil.WaitForLeader(t, s1.RPC)
   243  
   244  	// Create the register request for a periodic job.
   245  	job := mock.PeriodicJob()
   246  	req := &structs.JobRegisterRequest{
   247  		Job:          job,
   248  		WriteRequest: structs.WriteRequest{Region: "global"},
   249  	}
   250  
   251  	// Fetch the response
   252  	var resp structs.JobRegisterResponse
   253  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   254  		t.Fatalf("err: %v", err)
   255  	}
   256  	if resp.JobModifyIndex == 0 {
   257  		t.Fatalf("bad index: %d", resp.Index)
   258  	}
   259  
   260  	// Check for the node in the FSM
   261  	state := s1.fsm.State()
   262  	out, err := state.JobByID(job.ID)
   263  	if err != nil {
   264  		t.Fatalf("err: %v", err)
   265  	}
   266  	if out == nil {
   267  		t.Fatalf("expected job")
   268  	}
   269  	if out.CreateIndex != resp.JobModifyIndex {
   270  		t.Fatalf("index mis-match")
   271  	}
   272  	serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name
   273  	expectedServiceName := "web-frontend"
   274  	if serviceName != expectedServiceName {
   275  		t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName)
   276  	}
   277  
   278  	if resp.EvalID != "" {
   279  		t.Fatalf("Register created an eval for a periodic job")
   280  	}
   281  }
   282  
   283  func TestJobEndpoint_Evaluate(t *testing.T) {
   284  	s1 := testServer(t, func(c *Config) {
   285  		c.NumSchedulers = 0 // Prevent automatic dequeue
   286  	})
   287  	defer s1.Shutdown()
   288  	codec := rpcClient(t, s1)
   289  	testutil.WaitForLeader(t, s1.RPC)
   290  
   291  	// Create the register request
   292  	job := mock.Job()
   293  	req := &structs.JobRegisterRequest{
   294  		Job:          job,
   295  		WriteRequest: structs.WriteRequest{Region: "global"},
   296  	}
   297  
   298  	// Fetch the response
   299  	var resp structs.JobRegisterResponse
   300  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   301  		t.Fatalf("err: %v", err)
   302  	}
   303  	if resp.Index == 0 {
   304  		t.Fatalf("bad index: %d", resp.Index)
   305  	}
   306  
   307  	// Force a re-evaluation
   308  	reEval := &structs.JobEvaluateRequest{
   309  		JobID:        job.ID,
   310  		WriteRequest: structs.WriteRequest{Region: "global"},
   311  	}
   312  
   313  	// Fetch the response
   314  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err != nil {
   315  		t.Fatalf("err: %v", err)
   316  	}
   317  	if resp.Index == 0 {
   318  		t.Fatalf("bad index: %d", resp.Index)
   319  	}
   320  
   321  	// Lookup the evaluation
   322  	state := s1.fsm.State()
   323  	eval, err := state.EvalByID(resp.EvalID)
   324  	if err != nil {
   325  		t.Fatalf("err: %v", err)
   326  	}
   327  	if eval == nil {
   328  		t.Fatalf("expected eval")
   329  	}
   330  	if eval.CreateIndex != resp.EvalCreateIndex {
   331  		t.Fatalf("index mis-match")
   332  	}
   333  
   334  	if eval.Priority != job.Priority {
   335  		t.Fatalf("bad: %#v", eval)
   336  	}
   337  	if eval.Type != job.Type {
   338  		t.Fatalf("bad: %#v", eval)
   339  	}
   340  	if eval.TriggeredBy != structs.EvalTriggerJobRegister {
   341  		t.Fatalf("bad: %#v", eval)
   342  	}
   343  	if eval.JobID != job.ID {
   344  		t.Fatalf("bad: %#v", eval)
   345  	}
   346  	if eval.JobModifyIndex != resp.JobModifyIndex {
   347  		t.Fatalf("bad: %#v", eval)
   348  	}
   349  	if eval.Status != structs.EvalStatusPending {
   350  		t.Fatalf("bad: %#v", eval)
   351  	}
   352  }
   353  
   354  func TestJobEndpoint_Evaluate_Periodic(t *testing.T) {
   355  	s1 := testServer(t, func(c *Config) {
   356  		c.NumSchedulers = 0 // Prevent automatic dequeue
   357  	})
   358  	defer s1.Shutdown()
   359  	codec := rpcClient(t, s1)
   360  	testutil.WaitForLeader(t, s1.RPC)
   361  
   362  	// Create the register request
   363  	job := mock.PeriodicJob()
   364  	req := &structs.JobRegisterRequest{
   365  		Job:          job,
   366  		WriteRequest: structs.WriteRequest{Region: "global"},
   367  	}
   368  
   369  	// Fetch the response
   370  	var resp structs.JobRegisterResponse
   371  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   372  		t.Fatalf("err: %v", err)
   373  	}
   374  	if resp.JobModifyIndex == 0 {
   375  		t.Fatalf("bad index: %d", resp.Index)
   376  	}
   377  
   378  	// Force a re-evaluation
   379  	reEval := &structs.JobEvaluateRequest{
   380  		JobID:        job.ID,
   381  		WriteRequest: structs.WriteRequest{Region: "global"},
   382  	}
   383  
   384  	// Fetch the response
   385  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil {
   386  		t.Fatal("expect an err")
   387  	}
   388  }
   389  
   390  func TestJobEndpoint_Deregister(t *testing.T) {
   391  	s1 := testServer(t, func(c *Config) {
   392  		c.NumSchedulers = 0 // Prevent automatic dequeue
   393  	})
   394  	defer s1.Shutdown()
   395  	codec := rpcClient(t, s1)
   396  	testutil.WaitForLeader(t, s1.RPC)
   397  
   398  	// Create the register request
   399  	job := mock.Job()
   400  	reg := &structs.JobRegisterRequest{
   401  		Job:          job,
   402  		WriteRequest: structs.WriteRequest{Region: "global"},
   403  	}
   404  
   405  	// Fetch the response
   406  	var resp structs.JobRegisterResponse
   407  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
   408  		t.Fatalf("err: %v", err)
   409  	}
   410  
   411  	// Deregister
   412  	dereg := &structs.JobDeregisterRequest{
   413  		JobID:        job.ID,
   414  		WriteRequest: structs.WriteRequest{Region: "global"},
   415  	}
   416  	var resp2 structs.JobDeregisterResponse
   417  	if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil {
   418  		t.Fatalf("err: %v", err)
   419  	}
   420  	if resp2.Index == 0 {
   421  		t.Fatalf("bad index: %d", resp2.Index)
   422  	}
   423  
   424  	// Check for the node in the FSM
   425  	state := s1.fsm.State()
   426  	out, err := state.JobByID(job.ID)
   427  	if err != nil {
   428  		t.Fatalf("err: %v", err)
   429  	}
   430  	if out != nil {
   431  		t.Fatalf("unexpected job")
   432  	}
   433  
   434  	// Lookup the evaluation
   435  	eval, err := state.EvalByID(resp2.EvalID)
   436  	if err != nil {
   437  		t.Fatalf("err: %v", err)
   438  	}
   439  	if eval == nil {
   440  		t.Fatalf("expected eval")
   441  	}
   442  	if eval.CreateIndex != resp2.EvalCreateIndex {
   443  		t.Fatalf("index mis-match")
   444  	}
   445  
   446  	if eval.Priority != structs.JobDefaultPriority {
   447  		t.Fatalf("bad: %#v", eval)
   448  	}
   449  	if eval.Type != structs.JobTypeService {
   450  		t.Fatalf("bad: %#v", eval)
   451  	}
   452  	if eval.TriggeredBy != structs.EvalTriggerJobDeregister {
   453  		t.Fatalf("bad: %#v", eval)
   454  	}
   455  	if eval.JobID != job.ID {
   456  		t.Fatalf("bad: %#v", eval)
   457  	}
   458  	if eval.JobModifyIndex != resp2.JobModifyIndex {
   459  		t.Fatalf("bad: %#v", eval)
   460  	}
   461  	if eval.Status != structs.EvalStatusPending {
   462  		t.Fatalf("bad: %#v", eval)
   463  	}
   464  }
   465  
   466  func TestJobEndpoint_Deregister_Periodic(t *testing.T) {
   467  	s1 := testServer(t, func(c *Config) {
   468  		c.NumSchedulers = 0 // Prevent automatic dequeue
   469  	})
   470  	defer s1.Shutdown()
   471  	codec := rpcClient(t, s1)
   472  	testutil.WaitForLeader(t, s1.RPC)
   473  
   474  	// Create the register request
   475  	job := mock.PeriodicJob()
   476  	reg := &structs.JobRegisterRequest{
   477  		Job:          job,
   478  		WriteRequest: structs.WriteRequest{Region: "global"},
   479  	}
   480  
   481  	// Fetch the response
   482  	var resp structs.JobRegisterResponse
   483  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
   484  		t.Fatalf("err: %v", err)
   485  	}
   486  
   487  	// Deregister
   488  	dereg := &structs.JobDeregisterRequest{
   489  		JobID:        job.ID,
   490  		WriteRequest: structs.WriteRequest{Region: "global"},
   491  	}
   492  	var resp2 structs.JobDeregisterResponse
   493  	if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil {
   494  		t.Fatalf("err: %v", err)
   495  	}
   496  	if resp2.JobModifyIndex == 0 {
   497  		t.Fatalf("bad index: %d", resp2.Index)
   498  	}
   499  
   500  	// Check for the node in the FSM
   501  	state := s1.fsm.State()
   502  	out, err := state.JobByID(job.ID)
   503  	if err != nil {
   504  		t.Fatalf("err: %v", err)
   505  	}
   506  	if out != nil {
   507  		t.Fatalf("unexpected job")
   508  	}
   509  
   510  	if resp.EvalID != "" {
   511  		t.Fatalf("Deregister created an eval for a periodic job")
   512  	}
   513  }
   514  
   515  func TestJobEndpoint_GetJob(t *testing.T) {
   516  	s1 := testServer(t, nil)
   517  	defer s1.Shutdown()
   518  	codec := rpcClient(t, s1)
   519  	testutil.WaitForLeader(t, s1.RPC)
   520  
   521  	// Create the register request
   522  	job := mock.Job()
   523  	reg := &structs.JobRegisterRequest{
   524  		Job:          job,
   525  		WriteRequest: structs.WriteRequest{Region: "global"},
   526  	}
   527  
   528  	// Fetch the response
   529  	var resp structs.JobRegisterResponse
   530  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
   531  		t.Fatalf("err: %v", err)
   532  	}
   533  	job.CreateIndex = resp.JobModifyIndex
   534  	job.ModifyIndex = resp.JobModifyIndex
   535  	job.JobModifyIndex = resp.JobModifyIndex
   536  
   537  	// Lookup the job
   538  	get := &structs.JobSpecificRequest{
   539  		JobID:        job.ID,
   540  		QueryOptions: structs.QueryOptions{Region: "global"},
   541  	}
   542  	var resp2 structs.SingleJobResponse
   543  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil {
   544  		t.Fatalf("err: %v", err)
   545  	}
   546  	if resp2.Index != resp.JobModifyIndex {
   547  		t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index)
   548  	}
   549  
   550  	// Make a copy of the origin job and change the service name so that we can
   551  	// do a deep equal with the response from the GET JOB Api
   552  	j := job
   553  	j.TaskGroups[0].Tasks[0].Services[0].Name = "web-frontend"
   554  	for tgix, tg := range j.TaskGroups {
   555  		for tidx, t := range tg.Tasks {
   556  			for sidx, service := range t.Services {
   557  				for cidx, check := range service.Checks {
   558  					check.Name = resp2.Job.TaskGroups[tgix].Tasks[tidx].Services[sidx].Checks[cidx].Name
   559  				}
   560  			}
   561  		}
   562  	}
   563  
   564  	if !reflect.DeepEqual(j, resp2.Job) {
   565  		t.Fatalf("bad: %#v %#v", job, resp2.Job)
   566  	}
   567  
   568  	// Lookup non-existing job
   569  	get.JobID = "foobarbaz"
   570  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil {
   571  		t.Fatalf("err: %v", err)
   572  	}
   573  	if resp2.Index != resp.JobModifyIndex {
   574  		t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index)
   575  	}
   576  	if resp2.Job != nil {
   577  		t.Fatalf("unexpected job")
   578  	}
   579  }
   580  
   581  func TestJobEndpoint_GetJob_Blocking(t *testing.T) {
   582  	s1 := testServer(t, nil)
   583  	defer s1.Shutdown()
   584  	state := s1.fsm.State()
   585  	codec := rpcClient(t, s1)
   586  	testutil.WaitForLeader(t, s1.RPC)
   587  
   588  	// Create the jobs
   589  	job1 := mock.Job()
   590  	job2 := mock.Job()
   591  
   592  	// Upsert a job we are not interested in first.
   593  	time.AfterFunc(100*time.Millisecond, func() {
   594  		if err := state.UpsertJob(100, job1); err != nil {
   595  			t.Fatalf("err: %v", err)
   596  		}
   597  	})
   598  
   599  	// Upsert another job later which should trigger the watch.
   600  	time.AfterFunc(200*time.Millisecond, func() {
   601  		if err := state.UpsertJob(200, job2); err != nil {
   602  			t.Fatalf("err: %v", err)
   603  		}
   604  	})
   605  
   606  	req := &structs.JobSpecificRequest{
   607  		JobID: job2.ID,
   608  		QueryOptions: structs.QueryOptions{
   609  			Region:        "global",
   610  			MinQueryIndex: 50,
   611  		},
   612  	}
   613  	start := time.Now()
   614  	var resp structs.SingleJobResponse
   615  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp); err != nil {
   616  		t.Fatalf("err: %v", err)
   617  	}
   618  
   619  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
   620  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
   621  	}
   622  	if resp.Index != 200 {
   623  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
   624  	}
   625  	if resp.Job == nil || resp.Job.ID != job2.ID {
   626  		t.Fatalf("bad: %#v", resp.Job)
   627  	}
   628  
   629  	// Job delete fires watches
   630  	time.AfterFunc(100*time.Millisecond, func() {
   631  		if err := state.DeleteJob(300, job2.ID); err != nil {
   632  			t.Fatalf("err: %v", err)
   633  		}
   634  	})
   635  
   636  	req.QueryOptions.MinQueryIndex = 250
   637  	start = time.Now()
   638  
   639  	var resp2 structs.SingleJobResponse
   640  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp2); err != nil {
   641  		t.Fatalf("err: %v", err)
   642  	}
   643  
   644  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
   645  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
   646  	}
   647  	if resp2.Index != 300 {
   648  		t.Fatalf("Bad index: %d %d", resp2.Index, 300)
   649  	}
   650  	if resp2.Job != nil {
   651  		t.Fatalf("bad: %#v", resp2.Job)
   652  	}
   653  }
   654  
   655  func TestJobEndpoint_ListJobs(t *testing.T) {
   656  	s1 := testServer(t, nil)
   657  	defer s1.Shutdown()
   658  	codec := rpcClient(t, s1)
   659  	testutil.WaitForLeader(t, s1.RPC)
   660  
   661  	// Create the register request
   662  	job := mock.Job()
   663  	state := s1.fsm.State()
   664  	err := state.UpsertJob(1000, job)
   665  	if err != nil {
   666  		t.Fatalf("err: %v", err)
   667  	}
   668  
   669  	// Lookup the jobs
   670  	get := &structs.JobListRequest{
   671  		QueryOptions: structs.QueryOptions{Region: "global"},
   672  	}
   673  	var resp2 structs.JobListResponse
   674  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp2); err != nil {
   675  		t.Fatalf("err: %v", err)
   676  	}
   677  	if resp2.Index != 1000 {
   678  		t.Fatalf("Bad index: %d %d", resp2.Index, 1000)
   679  	}
   680  
   681  	if len(resp2.Jobs) != 1 {
   682  		t.Fatalf("bad: %#v", resp2.Jobs)
   683  	}
   684  	if resp2.Jobs[0].ID != job.ID {
   685  		t.Fatalf("bad: %#v", resp2.Jobs[0])
   686  	}
   687  
   688  	// Lookup the jobs by prefix
   689  	get = &structs.JobListRequest{
   690  		QueryOptions: structs.QueryOptions{Region: "global", Prefix: resp2.Jobs[0].ID[:4]},
   691  	}
   692  	var resp3 structs.JobListResponse
   693  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp3); err != nil {
   694  		t.Fatalf("err: %v", err)
   695  	}
   696  	if resp3.Index != 1000 {
   697  		t.Fatalf("Bad index: %d %d", resp3.Index, 1000)
   698  	}
   699  
   700  	if len(resp3.Jobs) != 1 {
   701  		t.Fatalf("bad: %#v", resp3.Jobs)
   702  	}
   703  	if resp3.Jobs[0].ID != job.ID {
   704  		t.Fatalf("bad: %#v", resp3.Jobs[0])
   705  	}
   706  }
   707  
   708  func TestJobEndpoint_ListJobs_Blocking(t *testing.T) {
   709  	s1 := testServer(t, nil)
   710  	defer s1.Shutdown()
   711  	state := s1.fsm.State()
   712  	codec := rpcClient(t, s1)
   713  	testutil.WaitForLeader(t, s1.RPC)
   714  
   715  	// Create the job
   716  	job := mock.Job()
   717  
   718  	// Upsert job triggers watches
   719  	time.AfterFunc(100*time.Millisecond, func() {
   720  		if err := state.UpsertJob(100, job); err != nil {
   721  			t.Fatalf("err: %v", err)
   722  		}
   723  	})
   724  
   725  	req := &structs.JobListRequest{
   726  		QueryOptions: structs.QueryOptions{
   727  			Region:        "global",
   728  			MinQueryIndex: 50,
   729  		},
   730  	}
   731  	start := time.Now()
   732  	var resp structs.JobListResponse
   733  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp); err != nil {
   734  		t.Fatalf("err: %v", err)
   735  	}
   736  
   737  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
   738  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
   739  	}
   740  	if resp.Index != 100 {
   741  		t.Fatalf("Bad index: %d %d", resp.Index, 100)
   742  	}
   743  	if len(resp.Jobs) != 1 || resp.Jobs[0].ID != job.ID {
   744  		t.Fatalf("bad: %#v", resp.Jobs)
   745  	}
   746  
   747  	// Job deletion triggers watches
   748  	time.AfterFunc(100*time.Millisecond, func() {
   749  		if err := state.DeleteJob(200, job.ID); err != nil {
   750  			t.Fatalf("err: %v", err)
   751  		}
   752  	})
   753  
   754  	req.MinQueryIndex = 150
   755  	start = time.Now()
   756  	var resp2 structs.JobListResponse
   757  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp2); err != nil {
   758  		t.Fatalf("err: %v", err)
   759  	}
   760  
   761  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
   762  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
   763  	}
   764  	if resp2.Index != 200 {
   765  		t.Fatalf("Bad index: %d %d", resp2.Index, 200)
   766  	}
   767  	if len(resp2.Jobs) != 0 {
   768  		t.Fatalf("bad: %#v", resp2.Jobs)
   769  	}
   770  }
   771  
   772  func TestJobEndpoint_Allocations(t *testing.T) {
   773  	s1 := testServer(t, nil)
   774  	defer s1.Shutdown()
   775  	codec := rpcClient(t, s1)
   776  	testutil.WaitForLeader(t, s1.RPC)
   777  
   778  	// Create the register request
   779  	alloc1 := mock.Alloc()
   780  	alloc2 := mock.Alloc()
   781  	alloc2.JobID = alloc1.JobID
   782  	state := s1.fsm.State()
   783  	err := state.UpsertAllocs(1000,
   784  		[]*structs.Allocation{alloc1, alloc2})
   785  	if err != nil {
   786  		t.Fatalf("err: %v", err)
   787  	}
   788  
   789  	// Lookup the jobs
   790  	get := &structs.JobSpecificRequest{
   791  		JobID:        alloc1.JobID,
   792  		QueryOptions: structs.QueryOptions{Region: "global"},
   793  	}
   794  	var resp2 structs.JobAllocationsResponse
   795  	if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp2); err != nil {
   796  		t.Fatalf("err: %v", err)
   797  	}
   798  	if resp2.Index != 1000 {
   799  		t.Fatalf("Bad index: %d %d", resp2.Index, 1000)
   800  	}
   801  
   802  	if len(resp2.Allocations) != 2 {
   803  		t.Fatalf("bad: %#v", resp2.Allocations)
   804  	}
   805  }
   806  
   807  func TestJobEndpoint_Allocations_Blocking(t *testing.T) {
   808  	s1 := testServer(t, nil)
   809  	defer s1.Shutdown()
   810  	codec := rpcClient(t, s1)
   811  	testutil.WaitForLeader(t, s1.RPC)
   812  
   813  	// Create the register request
   814  	alloc1 := mock.Alloc()
   815  	alloc2 := mock.Alloc()
   816  	alloc2.JobID = "job1"
   817  	state := s1.fsm.State()
   818  
   819  	// First upsert an unrelated alloc
   820  	time.AfterFunc(100*time.Millisecond, func() {
   821  		err := state.UpsertAllocs(100, []*structs.Allocation{alloc1})
   822  		if err != nil {
   823  			t.Fatalf("err: %v", err)
   824  		}
   825  	})
   826  
   827  	// Upsert an alloc for the job we are interested in later
   828  	time.AfterFunc(200*time.Millisecond, func() {
   829  		err := state.UpsertAllocs(200, []*structs.Allocation{alloc2})
   830  		if err != nil {
   831  			t.Fatalf("err: %v", err)
   832  		}
   833  	})
   834  
   835  	// Lookup the jobs
   836  	get := &structs.JobSpecificRequest{
   837  		JobID: "job1",
   838  		QueryOptions: structs.QueryOptions{
   839  			Region:        "global",
   840  			MinQueryIndex: 50,
   841  		},
   842  	}
   843  	var resp structs.JobAllocationsResponse
   844  	start := time.Now()
   845  	if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp); err != nil {
   846  		t.Fatalf("err: %v", err)
   847  	}
   848  
   849  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
   850  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
   851  	}
   852  	if resp.Index != 200 {
   853  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
   854  	}
   855  	if len(resp.Allocations) != 1 || resp.Allocations[0].JobID != "job1" {
   856  		t.Fatalf("bad: %#v", resp.Allocations)
   857  	}
   858  }
   859  
   860  func TestJobEndpoint_Evaluations(t *testing.T) {
   861  	s1 := testServer(t, nil)
   862  	defer s1.Shutdown()
   863  	codec := rpcClient(t, s1)
   864  	testutil.WaitForLeader(t, s1.RPC)
   865  
   866  	// Create the register request
   867  	eval1 := mock.Eval()
   868  	eval2 := mock.Eval()
   869  	eval2.JobID = eval1.JobID
   870  	state := s1.fsm.State()
   871  	err := state.UpsertEvals(1000,
   872  		[]*structs.Evaluation{eval1, eval2})
   873  	if err != nil {
   874  		t.Fatalf("err: %v", err)
   875  	}
   876  
   877  	// Lookup the jobs
   878  	get := &structs.JobSpecificRequest{
   879  		JobID:        eval1.JobID,
   880  		QueryOptions: structs.QueryOptions{Region: "global"},
   881  	}
   882  	var resp2 structs.JobEvaluationsResponse
   883  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp2); err != nil {
   884  		t.Fatalf("err: %v", err)
   885  	}
   886  	if resp2.Index != 1000 {
   887  		t.Fatalf("Bad index: %d %d", resp2.Index, 1000)
   888  	}
   889  
   890  	if len(resp2.Evaluations) != 2 {
   891  		t.Fatalf("bad: %#v", resp2.Evaluations)
   892  	}
   893  }