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

     1  package api
     2  
     3  import (
     4  	"reflect"
     5  	"sort"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/ncodes/nomad/helper"
    11  	"github.com/ncodes/nomad/testutil"
    12  )
    13  
    14  func TestJobs_Register(t *testing.T) {
    15  	c, s := makeClient(t, nil, nil)
    16  	defer s.Stop()
    17  	jobs := c.Jobs()
    18  
    19  	// Listing jobs before registering returns nothing
    20  	resp, qm, err := jobs.List(nil)
    21  	if err != nil {
    22  		t.Fatalf("err: %s", err)
    23  	}
    24  	if qm.LastIndex != 0 {
    25  		t.Fatalf("bad index: %d", qm.LastIndex)
    26  	}
    27  	if n := len(resp); n != 0 {
    28  		t.Fatalf("expected 0 jobs, got: %d", n)
    29  	}
    30  
    31  	// Create a job and attempt to register it
    32  	job := testJob()
    33  	eval, wm, err := jobs.Register(job, nil)
    34  	if err != nil {
    35  		t.Fatalf("err: %s", err)
    36  	}
    37  	if eval == "" {
    38  		t.Fatalf("missing eval id")
    39  	}
    40  	assertWriteMeta(t, wm)
    41  
    42  	// Query the jobs back out again
    43  	resp, qm, err = jobs.List(nil)
    44  	if err != nil {
    45  		t.Fatalf("err: %s", err)
    46  	}
    47  	assertQueryMeta(t, qm)
    48  
    49  	// Check that we got the expected response
    50  	if len(resp) != 1 || resp[0].ID != *job.ID {
    51  		t.Fatalf("bad: %#v", resp[0])
    52  	}
    53  }
    54  
    55  func TestJobs_Validate(t *testing.T) {
    56  	c, s := makeClient(t, nil, nil)
    57  	defer s.Stop()
    58  	jobs := c.Jobs()
    59  
    60  	// Create a job and attempt to register it
    61  	job := testJob()
    62  	resp, _, err := jobs.Validate(job, nil)
    63  	if err != nil {
    64  		t.Fatalf("err: %s", err)
    65  	}
    66  
    67  	if len(resp.ValidationErrors) != 0 {
    68  		t.Fatalf("bad %v", resp)
    69  	}
    70  
    71  	job.ID = nil
    72  	resp1, _, err := jobs.Validate(job, nil)
    73  	if err != nil {
    74  		t.Fatalf("err: %v", err)
    75  	}
    76  
    77  	if len(resp1.ValidationErrors) == 0 {
    78  		t.Fatalf("bad %v", resp1)
    79  	}
    80  }
    81  
    82  func TestJobs_Canonicalize(t *testing.T) {
    83  	testCases := []struct {
    84  		name     string
    85  		expected *Job
    86  		input    *Job
    87  	}{
    88  		{
    89  			name: "empty",
    90  			input: &Job{
    91  				TaskGroups: []*TaskGroup{
    92  					{
    93  						Tasks: []*Task{
    94  							{},
    95  						},
    96  					},
    97  				},
    98  			},
    99  			expected: &Job{
   100  				ID:                helper.StringToPtr(""),
   101  				Name:              helper.StringToPtr(""),
   102  				Region:            helper.StringToPtr("global"),
   103  				Type:              helper.StringToPtr("service"),
   104  				ParentID:          helper.StringToPtr(""),
   105  				Priority:          helper.IntToPtr(50),
   106  				AllAtOnce:         helper.BoolToPtr(false),
   107  				VaultToken:        helper.StringToPtr(""),
   108  				Status:            helper.StringToPtr(""),
   109  				StatusDescription: helper.StringToPtr(""),
   110  				CreateIndex:       helper.Uint64ToPtr(0),
   111  				ModifyIndex:       helper.Uint64ToPtr(0),
   112  				JobModifyIndex:    helper.Uint64ToPtr(0),
   113  				TaskGroups: []*TaskGroup{
   114  					{
   115  						Name:  helper.StringToPtr(""),
   116  						Count: helper.IntToPtr(1),
   117  						EphemeralDisk: &EphemeralDisk{
   118  							Sticky:  helper.BoolToPtr(false),
   119  							Migrate: helper.BoolToPtr(false),
   120  							SizeMB:  helper.IntToPtr(300),
   121  						},
   122  						RestartPolicy: &RestartPolicy{
   123  							Delay:    helper.TimeToPtr(15 * time.Second),
   124  							Attempts: helper.IntToPtr(2),
   125  							Interval: helper.TimeToPtr(1 * time.Minute),
   126  							Mode:     helper.StringToPtr("delay"),
   127  						},
   128  						Tasks: []*Task{
   129  							{
   130  								KillTimeout: helper.TimeToPtr(5 * time.Second),
   131  								LogConfig:   DefaultLogConfig(),
   132  								Resources:   MinResources(),
   133  							},
   134  						},
   135  					},
   136  				},
   137  			},
   138  		},
   139  		{
   140  			name: "partial",
   141  			input: &Job{
   142  				Name:     helper.StringToPtr("foo"),
   143  				ID:       helper.StringToPtr("bar"),
   144  				ParentID: helper.StringToPtr("lol"),
   145  				TaskGroups: []*TaskGroup{
   146  					{
   147  						Name: helper.StringToPtr("bar"),
   148  						Tasks: []*Task{
   149  							{
   150  								Name: "task1",
   151  							},
   152  						},
   153  					},
   154  				},
   155  			},
   156  			expected: &Job{
   157  				ID:                helper.StringToPtr("bar"),
   158  				Name:              helper.StringToPtr("foo"),
   159  				Region:            helper.StringToPtr("global"),
   160  				Type:              helper.StringToPtr("service"),
   161  				ParentID:          helper.StringToPtr("lol"),
   162  				Priority:          helper.IntToPtr(50),
   163  				AllAtOnce:         helper.BoolToPtr(false),
   164  				VaultToken:        helper.StringToPtr(""),
   165  				Status:            helper.StringToPtr(""),
   166  				StatusDescription: helper.StringToPtr(""),
   167  				CreateIndex:       helper.Uint64ToPtr(0),
   168  				ModifyIndex:       helper.Uint64ToPtr(0),
   169  				JobModifyIndex:    helper.Uint64ToPtr(0),
   170  				TaskGroups: []*TaskGroup{
   171  					{
   172  						Name:  helper.StringToPtr("bar"),
   173  						Count: helper.IntToPtr(1),
   174  						EphemeralDisk: &EphemeralDisk{
   175  							Sticky:  helper.BoolToPtr(false),
   176  							Migrate: helper.BoolToPtr(false),
   177  							SizeMB:  helper.IntToPtr(300),
   178  						},
   179  						RestartPolicy: &RestartPolicy{
   180  							Delay:    helper.TimeToPtr(15 * time.Second),
   181  							Attempts: helper.IntToPtr(2),
   182  							Interval: helper.TimeToPtr(1 * time.Minute),
   183  							Mode:     helper.StringToPtr("delay"),
   184  						},
   185  						Tasks: []*Task{
   186  							{
   187  								Name:        "task1",
   188  								LogConfig:   DefaultLogConfig(),
   189  								Resources:   MinResources(),
   190  								KillTimeout: helper.TimeToPtr(5 * time.Second),
   191  							},
   192  						},
   193  					},
   194  				},
   195  			},
   196  		},
   197  		{
   198  			name: "example_template",
   199  			input: &Job{
   200  				ID:          helper.StringToPtr("example_template"),
   201  				Name:        helper.StringToPtr("example_template"),
   202  				Datacenters: []string{"dc1"},
   203  				Type:        helper.StringToPtr("service"),
   204  				Update: &UpdateStrategy{
   205  					Stagger:     10 * time.Second,
   206  					MaxParallel: 1,
   207  				},
   208  				TaskGroups: []*TaskGroup{
   209  					{
   210  						Name:  helper.StringToPtr("cache"),
   211  						Count: helper.IntToPtr(1),
   212  						RestartPolicy: &RestartPolicy{
   213  							Interval: helper.TimeToPtr(5 * time.Minute),
   214  							Attempts: helper.IntToPtr(10),
   215  							Delay:    helper.TimeToPtr(25 * time.Second),
   216  							Mode:     helper.StringToPtr("delay"),
   217  						},
   218  						EphemeralDisk: &EphemeralDisk{
   219  							SizeMB: helper.IntToPtr(300),
   220  						},
   221  						Tasks: []*Task{
   222  							{
   223  								Name:   "redis",
   224  								Driver: "docker",
   225  								Config: map[string]interface{}{
   226  									"image": "redis:3.2",
   227  									"port_map": map[string]int{
   228  										"db": 6379,
   229  									},
   230  								},
   231  								Resources: &Resources{
   232  									CPU:      helper.IntToPtr(500),
   233  									MemoryMB: helper.IntToPtr(256),
   234  									Networks: []*NetworkResource{
   235  										{
   236  											MBits: helper.IntToPtr(10),
   237  											DynamicPorts: []Port{
   238  												{
   239  													Label: "db",
   240  												},
   241  											},
   242  										},
   243  									},
   244  								},
   245  								Services: []*Service{
   246  									{
   247  										Name:      "global-redis-check",
   248  										Tags:      []string{"global", "cache"},
   249  										PortLabel: "db",
   250  										Checks: []ServiceCheck{
   251  											{
   252  												Name:     "alive",
   253  												Type:     "tcp",
   254  												Interval: 10 * time.Second,
   255  												Timeout:  2 * time.Second,
   256  											},
   257  										},
   258  									},
   259  								},
   260  								Templates: []*Template{
   261  									{
   262  										EmbeddedTmpl: helper.StringToPtr("---"),
   263  										DestPath:     helper.StringToPtr("local/file.yml"),
   264  									},
   265  								},
   266  							},
   267  						},
   268  					},
   269  				},
   270  			},
   271  			expected: &Job{
   272  				ID:                helper.StringToPtr("example_template"),
   273  				Name:              helper.StringToPtr("example_template"),
   274  				ParentID:          helper.StringToPtr(""),
   275  				Priority:          helper.IntToPtr(50),
   276  				Region:            helper.StringToPtr("global"),
   277  				Type:              helper.StringToPtr("service"),
   278  				AllAtOnce:         helper.BoolToPtr(false),
   279  				VaultToken:        helper.StringToPtr(""),
   280  				Status:            helper.StringToPtr(""),
   281  				StatusDescription: helper.StringToPtr(""),
   282  				CreateIndex:       helper.Uint64ToPtr(0),
   283  				ModifyIndex:       helper.Uint64ToPtr(0),
   284  				JobModifyIndex:    helper.Uint64ToPtr(0),
   285  				Datacenters:       []string{"dc1"},
   286  				Update: &UpdateStrategy{
   287  					Stagger:     10 * time.Second,
   288  					MaxParallel: 1,
   289  				},
   290  				TaskGroups: []*TaskGroup{
   291  					{
   292  						Name:  helper.StringToPtr("cache"),
   293  						Count: helper.IntToPtr(1),
   294  						RestartPolicy: &RestartPolicy{
   295  							Interval: helper.TimeToPtr(5 * time.Minute),
   296  							Attempts: helper.IntToPtr(10),
   297  							Delay:    helper.TimeToPtr(25 * time.Second),
   298  							Mode:     helper.StringToPtr("delay"),
   299  						},
   300  						EphemeralDisk: &EphemeralDisk{
   301  							Sticky:  helper.BoolToPtr(false),
   302  							Migrate: helper.BoolToPtr(false),
   303  							SizeMB:  helper.IntToPtr(300),
   304  						},
   305  						Tasks: []*Task{
   306  							{
   307  								Name:   "redis",
   308  								Driver: "docker",
   309  								Config: map[string]interface{}{
   310  									"image": "redis:3.2",
   311  									"port_map": map[string]int{
   312  										"db": 6379,
   313  									},
   314  								},
   315  								Resources: &Resources{
   316  									CPU:      helper.IntToPtr(500),
   317  									MemoryMB: helper.IntToPtr(256),
   318  									IOPS:     helper.IntToPtr(0),
   319  									Networks: []*NetworkResource{
   320  										{
   321  											MBits: helper.IntToPtr(10),
   322  											DynamicPorts: []Port{
   323  												{
   324  													Label: "db",
   325  												},
   326  											},
   327  										},
   328  									},
   329  								},
   330  								Services: []*Service{
   331  									{
   332  										Name:      "global-redis-check",
   333  										Tags:      []string{"global", "cache"},
   334  										PortLabel: "db",
   335  										Checks: []ServiceCheck{
   336  											{
   337  												Name:     "alive",
   338  												Type:     "tcp",
   339  												Interval: 10 * time.Second,
   340  												Timeout:  2 * time.Second,
   341  											},
   342  										},
   343  									},
   344  								},
   345  								KillTimeout: helper.TimeToPtr(5 * time.Second),
   346  								LogConfig:   DefaultLogConfig(),
   347  								Templates: []*Template{
   348  									{
   349  										SourcePath:   helper.StringToPtr(""),
   350  										DestPath:     helper.StringToPtr("local/file.yml"),
   351  										EmbeddedTmpl: helper.StringToPtr("---"),
   352  										ChangeMode:   helper.StringToPtr("restart"),
   353  										ChangeSignal: helper.StringToPtr(""),
   354  										Splay:        helper.TimeToPtr(5 * time.Second),
   355  										Perms:        helper.StringToPtr("0644"),
   356  										LeftDelim:    helper.StringToPtr("{{"),
   357  										RightDelim:   helper.StringToPtr("}}"),
   358  									},
   359  								},
   360  							},
   361  						},
   362  					},
   363  				},
   364  			},
   365  		},
   366  
   367  		{
   368  			name: "periodic",
   369  			input: &Job{
   370  				ID:       helper.StringToPtr("bar"),
   371  				Periodic: &PeriodicConfig{},
   372  			},
   373  			expected: &Job{
   374  				ID:                helper.StringToPtr("bar"),
   375  				ParentID:          helper.StringToPtr(""),
   376  				Name:              helper.StringToPtr("bar"),
   377  				Region:            helper.StringToPtr("global"),
   378  				Type:              helper.StringToPtr("service"),
   379  				Priority:          helper.IntToPtr(50),
   380  				AllAtOnce:         helper.BoolToPtr(false),
   381  				VaultToken:        helper.StringToPtr(""),
   382  				Status:            helper.StringToPtr(""),
   383  				StatusDescription: helper.StringToPtr(""),
   384  				CreateIndex:       helper.Uint64ToPtr(0),
   385  				ModifyIndex:       helper.Uint64ToPtr(0),
   386  				JobModifyIndex:    helper.Uint64ToPtr(0),
   387  				Periodic: &PeriodicConfig{
   388  					Enabled:         helper.BoolToPtr(true),
   389  					Spec:            helper.StringToPtr(""),
   390  					SpecType:        helper.StringToPtr(PeriodicSpecCron),
   391  					ProhibitOverlap: helper.BoolToPtr(false),
   392  					TimeZone:        helper.StringToPtr("UTC"),
   393  				},
   394  			},
   395  		},
   396  	}
   397  
   398  	for _, tc := range testCases {
   399  		t.Run(tc.name, func(t *testing.T) {
   400  			tc.input.Canonicalize()
   401  			if !reflect.DeepEqual(tc.input, tc.expected) {
   402  				t.Fatalf("Name: %v, expected:\n%#v\nactual:\n%#v", tc.name, tc.expected, tc.input)
   403  			}
   404  		})
   405  	}
   406  }
   407  
   408  func TestJobs_EnforceRegister(t *testing.T) {
   409  	c, s := makeClient(t, nil, nil)
   410  	defer s.Stop()
   411  	jobs := c.Jobs()
   412  
   413  	// Listing jobs before registering returns nothing
   414  	resp, qm, err := jobs.List(nil)
   415  	if err != nil {
   416  		t.Fatalf("err: %s", err)
   417  	}
   418  	if qm.LastIndex != 0 {
   419  		t.Fatalf("bad index: %d", qm.LastIndex)
   420  	}
   421  	if n := len(resp); n != 0 {
   422  		t.Fatalf("expected 0 jobs, got: %d", n)
   423  	}
   424  
   425  	// Create a job and attempt to register it with an incorrect index.
   426  	job := testJob()
   427  	eval, wm, err := jobs.EnforceRegister(job, 10, nil)
   428  	if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) {
   429  		t.Fatalf("expected enforcement error: %v", err)
   430  	}
   431  
   432  	// Register
   433  	eval, wm, err = jobs.EnforceRegister(job, 0, nil)
   434  	if err != nil {
   435  		t.Fatalf("err: %s", err)
   436  	}
   437  	if eval == "" {
   438  		t.Fatalf("missing eval id")
   439  	}
   440  	assertWriteMeta(t, wm)
   441  
   442  	// Query the jobs back out again
   443  	resp, qm, err = jobs.List(nil)
   444  	if err != nil {
   445  		t.Fatalf("err: %s", err)
   446  	}
   447  	assertQueryMeta(t, qm)
   448  
   449  	// Check that we got the expected response
   450  	if len(resp) != 1 {
   451  		t.Fatalf("bad length: %d", len(resp))
   452  	}
   453  
   454  	if resp[0].ID != *job.ID {
   455  		t.Fatalf("bad: %#v", resp[0])
   456  	}
   457  	curIndex := resp[0].JobModifyIndex
   458  
   459  	// Fail at incorrect index
   460  	eval, wm, err = jobs.EnforceRegister(job, 123456, nil)
   461  	if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) {
   462  		t.Fatalf("expected enforcement error: %v", err)
   463  	}
   464  
   465  	// Works at correct index
   466  	eval, wm, err = jobs.EnforceRegister(job, curIndex, nil)
   467  	if err != nil {
   468  		t.Fatalf("err: %s", err)
   469  	}
   470  	if eval == "" {
   471  		t.Fatalf("missing eval id")
   472  	}
   473  	assertWriteMeta(t, wm)
   474  }
   475  
   476  func TestJobs_Info(t *testing.T) {
   477  	c, s := makeClient(t, nil, nil)
   478  	defer s.Stop()
   479  	jobs := c.Jobs()
   480  
   481  	// Trying to retrieve a job by ID before it exists
   482  	// returns an error
   483  	_, _, err := jobs.Info("job1", nil)
   484  	if err == nil || !strings.Contains(err.Error(), "not found") {
   485  		t.Fatalf("expected not found error, got: %#v", err)
   486  	}
   487  
   488  	// Register the job
   489  	job := testJob()
   490  	_, wm, err := jobs.Register(job, nil)
   491  	if err != nil {
   492  		t.Fatalf("err: %s", err)
   493  	}
   494  	assertWriteMeta(t, wm)
   495  
   496  	// Query the job again and ensure it exists
   497  	result, qm, err := jobs.Info("job1", nil)
   498  	if err != nil {
   499  		t.Fatalf("err: %s", err)
   500  	}
   501  	assertQueryMeta(t, qm)
   502  
   503  	// Check that the result is what we expect
   504  	if result == nil || *result.ID != *job.ID {
   505  		t.Fatalf("expect: %#v, got: %#v", job, result)
   506  	}
   507  }
   508  
   509  func TestJobs_PrefixList(t *testing.T) {
   510  	c, s := makeClient(t, nil, nil)
   511  	defer s.Stop()
   512  	jobs := c.Jobs()
   513  
   514  	// Listing when nothing exists returns empty
   515  	results, qm, err := jobs.PrefixList("dummy")
   516  	if err != nil {
   517  		t.Fatalf("err: %s", err)
   518  	}
   519  	if qm.LastIndex != 0 {
   520  		t.Fatalf("bad index: %d", qm.LastIndex)
   521  	}
   522  	if n := len(results); n != 0 {
   523  		t.Fatalf("expected 0 jobs, got: %d", n)
   524  	}
   525  
   526  	// Register the job
   527  	job := testJob()
   528  	_, wm, err := jobs.Register(job, nil)
   529  	if err != nil {
   530  		t.Fatalf("err: %s", err)
   531  	}
   532  	assertWriteMeta(t, wm)
   533  
   534  	// Query the job again and ensure it exists
   535  	// Listing when nothing exists returns empty
   536  	results, qm, err = jobs.PrefixList((*job.ID)[:1])
   537  	if err != nil {
   538  		t.Fatalf("err: %s", err)
   539  	}
   540  
   541  	// Check if we have the right list
   542  	if len(results) != 1 || results[0].ID != *job.ID {
   543  		t.Fatalf("bad: %#v", results)
   544  	}
   545  }
   546  
   547  func TestJobs_List(t *testing.T) {
   548  	c, s := makeClient(t, nil, nil)
   549  	defer s.Stop()
   550  	jobs := c.Jobs()
   551  
   552  	// Listing when nothing exists returns empty
   553  	results, qm, err := jobs.List(nil)
   554  	if err != nil {
   555  		t.Fatalf("err: %s", err)
   556  	}
   557  	if qm.LastIndex != 0 {
   558  		t.Fatalf("bad index: %d", qm.LastIndex)
   559  	}
   560  	if n := len(results); n != 0 {
   561  		t.Fatalf("expected 0 jobs, got: %d", n)
   562  	}
   563  
   564  	// Register the job
   565  	job := testJob()
   566  	_, wm, err := jobs.Register(job, nil)
   567  	if err != nil {
   568  		t.Fatalf("err: %s", err)
   569  	}
   570  	assertWriteMeta(t, wm)
   571  
   572  	// Query the job again and ensure it exists
   573  	// Listing when nothing exists returns empty
   574  	results, qm, err = jobs.List(nil)
   575  	if err != nil {
   576  		t.Fatalf("err: %s", err)
   577  	}
   578  
   579  	// Check if we have the right list
   580  	if len(results) != 1 || results[0].ID != *job.ID {
   581  		t.Fatalf("bad: %#v", results)
   582  	}
   583  }
   584  
   585  func TestJobs_Allocations(t *testing.T) {
   586  	c, s := makeClient(t, nil, nil)
   587  	defer s.Stop()
   588  	jobs := c.Jobs()
   589  
   590  	// Looking up by a non-existent job returns nothing
   591  	allocs, qm, err := jobs.Allocations("job1", true, nil)
   592  	if err != nil {
   593  		t.Fatalf("err: %s", err)
   594  	}
   595  	if qm.LastIndex != 0 {
   596  		t.Fatalf("bad index: %d", qm.LastIndex)
   597  	}
   598  	if n := len(allocs); n != 0 {
   599  		t.Fatalf("expected 0 allocs, got: %d", n)
   600  	}
   601  
   602  	// TODO: do something here to create some allocations for
   603  	// an existing job, lookup again.
   604  }
   605  
   606  func TestJobs_Evaluations(t *testing.T) {
   607  	c, s := makeClient(t, nil, nil)
   608  	defer s.Stop()
   609  	jobs := c.Jobs()
   610  
   611  	// Looking up by a non-existent job ID returns nothing
   612  	evals, qm, err := jobs.Evaluations("job1", nil)
   613  	if err != nil {
   614  		t.Fatalf("err: %s", err)
   615  	}
   616  	if qm.LastIndex != 0 {
   617  		t.Fatalf("bad index: %d", qm.LastIndex)
   618  	}
   619  	if n := len(evals); n != 0 {
   620  		t.Fatalf("expected 0 evals, got: %d", n)
   621  	}
   622  
   623  	// Insert a job. This also creates an evaluation so we should
   624  	// be able to query that out after.
   625  	job := testJob()
   626  	evalID, wm, err := jobs.Register(job, nil)
   627  	if err != nil {
   628  		t.Fatalf("err: %s", err)
   629  	}
   630  	assertWriteMeta(t, wm)
   631  
   632  	// Look up the evaluations again.
   633  	evals, qm, err = jobs.Evaluations("job1", nil)
   634  	if err != nil {
   635  		t.Fatalf("err: %s", err)
   636  	}
   637  	assertQueryMeta(t, qm)
   638  
   639  	// Check that we got the evals back, evals are in order most recent to least recent
   640  	// so the last eval is the original registered eval
   641  	idx := len(evals) - 1
   642  	if n := len(evals); n == 0 || evals[idx].ID != evalID {
   643  		t.Fatalf("expected >= 1 eval (%s), got: %#v", evalID, evals[idx])
   644  	}
   645  }
   646  
   647  func TestJobs_Deregister(t *testing.T) {
   648  	c, s := makeClient(t, nil, nil)
   649  	defer s.Stop()
   650  	jobs := c.Jobs()
   651  
   652  	// Register a new job
   653  	job := testJob()
   654  	_, wm, err := jobs.Register(job, nil)
   655  	if err != nil {
   656  		t.Fatalf("err: %s", err)
   657  	}
   658  	assertWriteMeta(t, wm)
   659  
   660  	// Attempting delete on non-existing job returns an error
   661  	if _, _, err = jobs.Deregister("nope", nil); err != nil {
   662  		t.Fatalf("unexpected error deregistering job: %v", err)
   663  
   664  	}
   665  
   666  	// Deleting an existing job works
   667  	evalID, wm3, err := jobs.Deregister("job1", nil)
   668  	if err != nil {
   669  		t.Fatalf("err: %s", err)
   670  	}
   671  	assertWriteMeta(t, wm3)
   672  	if evalID == "" {
   673  		t.Fatalf("missing eval ID")
   674  	}
   675  
   676  	// Check that the job is really gone
   677  	result, qm, err := jobs.List(nil)
   678  	if err != nil {
   679  		t.Fatalf("err: %s", err)
   680  	}
   681  	assertQueryMeta(t, qm)
   682  	if n := len(result); n != 0 {
   683  		t.Fatalf("expected 0 jobs, got: %d", n)
   684  	}
   685  }
   686  
   687  func TestJobs_ForceEvaluate(t *testing.T) {
   688  	c, s := makeClient(t, nil, nil)
   689  	defer s.Stop()
   690  	jobs := c.Jobs()
   691  
   692  	// Force-eval on a non-existent job fails
   693  	_, _, err := jobs.ForceEvaluate("job1", nil)
   694  	if err == nil || !strings.Contains(err.Error(), "not found") {
   695  		t.Fatalf("expected not found error, got: %#v", err)
   696  	}
   697  
   698  	// Create a new job
   699  	_, wm, err := jobs.Register(testJob(), nil)
   700  	if err != nil {
   701  		t.Fatalf("err: %s", err)
   702  	}
   703  	assertWriteMeta(t, wm)
   704  
   705  	// Try force-eval again
   706  	evalID, wm, err := jobs.ForceEvaluate("job1", nil)
   707  	if err != nil {
   708  		t.Fatalf("err: %s", err)
   709  	}
   710  	assertWriteMeta(t, wm)
   711  
   712  	// Retrieve the evals and see if we get a matching one
   713  	evals, qm, err := jobs.Evaluations("job1", nil)
   714  	if err != nil {
   715  		t.Fatalf("err: %s", err)
   716  	}
   717  	assertQueryMeta(t, qm)
   718  	for _, eval := range evals {
   719  		if eval.ID == evalID {
   720  			return
   721  		}
   722  	}
   723  	t.Fatalf("evaluation %q missing", evalID)
   724  }
   725  
   726  func TestJobs_PeriodicForce(t *testing.T) {
   727  	c, s := makeClient(t, nil, nil)
   728  	defer s.Stop()
   729  	jobs := c.Jobs()
   730  
   731  	// Force-eval on a non-existent job fails
   732  	_, _, err := jobs.PeriodicForce("job1", nil)
   733  	if err == nil || !strings.Contains(err.Error(), "not found") {
   734  		t.Fatalf("expected not found error, got: %#v", err)
   735  	}
   736  
   737  	// Create a new job
   738  	job := testPeriodicJob()
   739  	_, _, err = jobs.Register(job, nil)
   740  	if err != nil {
   741  		t.Fatalf("err: %s", err)
   742  	}
   743  
   744  	testutil.WaitForResult(func() (bool, error) {
   745  		out, _, err := jobs.Info(*job.ID, nil)
   746  		if err != nil || out == nil || *out.ID != *job.ID {
   747  			return false, err
   748  		}
   749  		return true, nil
   750  	}, func(err error) {
   751  		t.Fatalf("err: %s", err)
   752  	})
   753  
   754  	// Try force again
   755  	evalID, wm, err := jobs.PeriodicForce(*job.ID, nil)
   756  	if err != nil {
   757  		t.Fatalf("err: %s", err)
   758  	}
   759  	assertWriteMeta(t, wm)
   760  
   761  	if evalID == "" {
   762  		t.Fatalf("empty evalID")
   763  	}
   764  
   765  	// Retrieve the eval
   766  	evals := c.Evaluations()
   767  	eval, qm, err := evals.Info(evalID, nil)
   768  	if err != nil {
   769  		t.Fatalf("err: %s", err)
   770  	}
   771  	assertQueryMeta(t, qm)
   772  	if eval.ID == evalID {
   773  		return
   774  	}
   775  	t.Fatalf("evaluation %q missing", evalID)
   776  }
   777  
   778  func TestJobs_Plan(t *testing.T) {
   779  	c, s := makeClient(t, nil, nil)
   780  	defer s.Stop()
   781  	jobs := c.Jobs()
   782  
   783  	// Create a job and attempt to register it
   784  	job := testJob()
   785  	eval, wm, err := jobs.Register(job, nil)
   786  	if err != nil {
   787  		t.Fatalf("err: %s", err)
   788  	}
   789  	if eval == "" {
   790  		t.Fatalf("missing eval id")
   791  	}
   792  	assertWriteMeta(t, wm)
   793  
   794  	// Check that passing a nil job fails
   795  	if _, _, err := jobs.Plan(nil, true, nil); err == nil {
   796  		t.Fatalf("expect an error when job isn't provided")
   797  	}
   798  
   799  	// Make a plan request
   800  	planResp, wm, err := jobs.Plan(job, true, nil)
   801  	if err != nil {
   802  		t.Fatalf("err: %s", err)
   803  	}
   804  	if planResp == nil {
   805  		t.Fatalf("nil response")
   806  	}
   807  
   808  	if planResp.JobModifyIndex == 0 {
   809  		t.Fatalf("bad JobModifyIndex value: %#v", planResp)
   810  	}
   811  	if planResp.Diff == nil {
   812  		t.Fatalf("got nil diff: %#v", planResp)
   813  	}
   814  	if planResp.Annotations == nil {
   815  		t.Fatalf("got nil annotations: %#v", planResp)
   816  	}
   817  	// Can make this assertion because there are no clients.
   818  	if len(planResp.CreatedEvals) == 0 {
   819  		t.Fatalf("got no CreatedEvals: %#v", planResp)
   820  	}
   821  
   822  	// Make a plan request w/o the diff
   823  	planResp, wm, err = jobs.Plan(job, false, nil)
   824  	if err != nil {
   825  		t.Fatalf("err: %s", err)
   826  	}
   827  	assertWriteMeta(t, wm)
   828  
   829  	if planResp == nil {
   830  		t.Fatalf("nil response")
   831  	}
   832  
   833  	if planResp.JobModifyIndex == 0 {
   834  		t.Fatalf("bad JobModifyIndex value: %d", planResp.JobModifyIndex)
   835  	}
   836  	if planResp.Diff != nil {
   837  		t.Fatalf("got non-nil diff: %#v", planResp)
   838  	}
   839  	if planResp.Annotations == nil {
   840  		t.Fatalf("got nil annotations: %#v", planResp)
   841  	}
   842  	// Can make this assertion because there are no clients.
   843  	if len(planResp.CreatedEvals) == 0 {
   844  		t.Fatalf("got no CreatedEvals: %#v", planResp)
   845  	}
   846  }
   847  
   848  func TestJobs_JobSummary(t *testing.T) {
   849  	c, s := makeClient(t, nil, nil)
   850  	defer s.Stop()
   851  	jobs := c.Jobs()
   852  
   853  	// Trying to retrieve a job summary before the job exists
   854  	// returns an error
   855  	_, _, err := jobs.Summary("job1", nil)
   856  	if err == nil || !strings.Contains(err.Error(), "not found") {
   857  		t.Fatalf("expected not found error, got: %#v", err)
   858  	}
   859  
   860  	// Register the job
   861  	job := testJob()
   862  	taskName := job.TaskGroups[0].Name
   863  	_, wm, err := jobs.Register(job, nil)
   864  	if err != nil {
   865  		t.Fatalf("err: %s", err)
   866  	}
   867  	assertWriteMeta(t, wm)
   868  
   869  	// Query the job summary again and ensure it exists
   870  	result, qm, err := jobs.Summary("job1", nil)
   871  	if err != nil {
   872  		t.Fatalf("err: %s", err)
   873  	}
   874  	assertQueryMeta(t, qm)
   875  
   876  	// Check that the result is what we expect
   877  	if *job.ID != result.JobID {
   878  		t.Fatalf("err: expected job id of %s saw %s", *job.ID, result.JobID)
   879  	}
   880  	if _, ok := result.Summary[*taskName]; !ok {
   881  		t.Fatalf("err: unable to find %s key in job summary", *taskName)
   882  	}
   883  }
   884  
   885  func TestJobs_NewBatchJob(t *testing.T) {
   886  	job := NewBatchJob("job1", "myjob", "region1", 5)
   887  	expect := &Job{
   888  		Region:   helper.StringToPtr("region1"),
   889  		ID:       helper.StringToPtr("job1"),
   890  		Name:     helper.StringToPtr("myjob"),
   891  		Type:     helper.StringToPtr(JobTypeBatch),
   892  		Priority: helper.IntToPtr(5),
   893  	}
   894  	if !reflect.DeepEqual(job, expect) {
   895  		t.Fatalf("expect: %#v, got: %#v", expect, job)
   896  	}
   897  }
   898  
   899  func TestJobs_NewServiceJob(t *testing.T) {
   900  	job := NewServiceJob("job1", "myjob", "region1", 5)
   901  	expect := &Job{
   902  		Region:   helper.StringToPtr("region1"),
   903  		ID:       helper.StringToPtr("job1"),
   904  		Name:     helper.StringToPtr("myjob"),
   905  		Type:     helper.StringToPtr(JobTypeService),
   906  		Priority: helper.IntToPtr(5),
   907  	}
   908  	if !reflect.DeepEqual(job, expect) {
   909  		t.Fatalf("expect: %#v, got: %#v", expect, job)
   910  	}
   911  }
   912  
   913  func TestJobs_SetMeta(t *testing.T) {
   914  	job := &Job{Meta: nil}
   915  
   916  	// Initializes a nil map
   917  	out := job.SetMeta("foo", "bar")
   918  	if job.Meta == nil {
   919  		t.Fatalf("should initialize metadata")
   920  	}
   921  
   922  	// Check that the job was returned
   923  	if job != out {
   924  		t.Fatalf("expect: %#v, got: %#v", job, out)
   925  	}
   926  
   927  	// Setting another pair is additive
   928  	job.SetMeta("baz", "zip")
   929  	expect := map[string]string{"foo": "bar", "baz": "zip"}
   930  	if !reflect.DeepEqual(job.Meta, expect) {
   931  		t.Fatalf("expect: %#v, got: %#v", expect, job.Meta)
   932  	}
   933  }
   934  
   935  func TestJobs_Constrain(t *testing.T) {
   936  	job := &Job{Constraints: nil}
   937  
   938  	// Create and add a constraint
   939  	out := job.Constrain(NewConstraint("kernel.name", "=", "darwin"))
   940  	if n := len(job.Constraints); n != 1 {
   941  		t.Fatalf("expected 1 constraint, got: %d", n)
   942  	}
   943  
   944  	// Check that the job was returned
   945  	if job != out {
   946  		t.Fatalf("expect: %#v, got: %#v", job, out)
   947  	}
   948  
   949  	// Adding another constraint preserves the original
   950  	job.Constrain(NewConstraint("memory.totalbytes", ">=", "128000000"))
   951  	expect := []*Constraint{
   952  		&Constraint{
   953  			LTarget: "kernel.name",
   954  			RTarget: "darwin",
   955  			Operand: "=",
   956  		},
   957  		&Constraint{
   958  			LTarget: "memory.totalbytes",
   959  			RTarget: "128000000",
   960  			Operand: ">=",
   961  		},
   962  	}
   963  	if !reflect.DeepEqual(job.Constraints, expect) {
   964  		t.Fatalf("expect: %#v, got: %#v", expect, job.Constraints)
   965  	}
   966  }
   967  
   968  func TestJobs_Sort(t *testing.T) {
   969  	jobs := []*JobListStub{
   970  		&JobListStub{ID: "job2"},
   971  		&JobListStub{ID: "job0"},
   972  		&JobListStub{ID: "job1"},
   973  	}
   974  	sort.Sort(JobIDSort(jobs))
   975  
   976  	expect := []*JobListStub{
   977  		&JobListStub{ID: "job0"},
   978  		&JobListStub{ID: "job1"},
   979  		&JobListStub{ID: "job2"},
   980  	}
   981  	if !reflect.DeepEqual(jobs, expect) {
   982  		t.Fatalf("\n\n%#v\n\n%#v", jobs, expect)
   983  	}
   984  }