github.com/djenriquez/nomad-1@v0.8.1/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/hashicorp/nomad/helper"
    11  	"github.com/hashicorp/nomad/nomad/mock"
    12  	"github.com/hashicorp/nomad/testutil"
    13  	"github.com/kr/pretty"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func TestJobs_Register(t *testing.T) {
    19  	t.Parallel()
    20  	require := require.New(t)
    21  
    22  	c, s := makeClient(t, nil, nil)
    23  	defer s.Stop()
    24  	jobs := c.Jobs()
    25  
    26  	// Listing jobs before registering returns nothing
    27  	resp, _, err := jobs.List(nil)
    28  	require.Nil(err)
    29  	require.Emptyf(resp, "expected 0 jobs, got: %d", len(resp))
    30  
    31  	// Create a job and attempt to register it
    32  	job := testJob()
    33  	resp2, wm, err := jobs.Register(job, nil)
    34  	require.Nil(err)
    35  	require.NotNil(resp2)
    36  	require.NotEmpty(resp2.EvalID)
    37  	assertWriteMeta(t, wm)
    38  
    39  	// Query the jobs back out again
    40  	resp, qm, err := jobs.List(nil)
    41  	assertQueryMeta(t, qm)
    42  	require.Nil(err)
    43  
    44  	// Check that we got the expected response
    45  	if len(resp) != 1 || resp[0].ID != *job.ID {
    46  		t.Fatalf("bad: %#v", resp[0])
    47  	}
    48  }
    49  
    50  func TestJobs_Validate(t *testing.T) {
    51  	t.Parallel()
    52  	c, s := makeClient(t, nil, nil)
    53  	defer s.Stop()
    54  	jobs := c.Jobs()
    55  
    56  	// Create a job and attempt to register it
    57  	job := testJob()
    58  	resp, _, err := jobs.Validate(job, nil)
    59  	if err != nil {
    60  		t.Fatalf("err: %s", err)
    61  	}
    62  
    63  	if len(resp.ValidationErrors) != 0 {
    64  		t.Fatalf("bad %v", resp)
    65  	}
    66  
    67  	job.ID = nil
    68  	resp1, _, err := jobs.Validate(job, nil)
    69  	if err != nil {
    70  		t.Fatalf("err: %v", err)
    71  	}
    72  
    73  	if len(resp1.ValidationErrors) == 0 {
    74  		t.Fatalf("bad %v", resp1)
    75  	}
    76  }
    77  
    78  func TestJobs_Canonicalize(t *testing.T) {
    79  	t.Parallel()
    80  	testCases := []struct {
    81  		name     string
    82  		expected *Job
    83  		input    *Job
    84  	}{
    85  		{
    86  			name: "empty",
    87  			input: &Job{
    88  				TaskGroups: []*TaskGroup{
    89  					{
    90  						Tasks: []*Task{
    91  							{},
    92  						},
    93  					},
    94  				},
    95  			},
    96  			expected: &Job{
    97  				ID:                helper.StringToPtr(""),
    98  				Name:              helper.StringToPtr(""),
    99  				Region:            helper.StringToPtr("global"),
   100  				Namespace:         helper.StringToPtr(DefaultNamespace),
   101  				Type:              helper.StringToPtr("service"),
   102  				ParentID:          helper.StringToPtr(""),
   103  				Priority:          helper.IntToPtr(50),
   104  				AllAtOnce:         helper.BoolToPtr(false),
   105  				VaultToken:        helper.StringToPtr(""),
   106  				Status:            helper.StringToPtr(""),
   107  				StatusDescription: helper.StringToPtr(""),
   108  				Stop:              helper.BoolToPtr(false),
   109  				Stable:            helper.BoolToPtr(false),
   110  				Version:           helper.Uint64ToPtr(0),
   111  				CreateIndex:       helper.Uint64ToPtr(0),
   112  				ModifyIndex:       helper.Uint64ToPtr(0),
   113  				JobModifyIndex:    helper.Uint64ToPtr(0),
   114  				TaskGroups: []*TaskGroup{
   115  					{
   116  						Name:  helper.StringToPtr(""),
   117  						Count: helper.IntToPtr(1),
   118  						EphemeralDisk: &EphemeralDisk{
   119  							Sticky:  helper.BoolToPtr(false),
   120  							Migrate: helper.BoolToPtr(false),
   121  							SizeMB:  helper.IntToPtr(300),
   122  						},
   123  						RestartPolicy: &RestartPolicy{
   124  							Delay:    helper.TimeToPtr(15 * time.Second),
   125  							Attempts: helper.IntToPtr(2),
   126  							Interval: helper.TimeToPtr(30 * time.Minute),
   127  							Mode:     helper.StringToPtr("fail"),
   128  						},
   129  						ReschedulePolicy: &ReschedulePolicy{
   130  							Attempts:      helper.IntToPtr(0),
   131  							Interval:      helper.TimeToPtr(0),
   132  							DelayFunction: helper.StringToPtr("exponential"),
   133  							Delay:         helper.TimeToPtr(30 * time.Second),
   134  							MaxDelay:      helper.TimeToPtr(1 * time.Hour),
   135  							Unlimited:     helper.BoolToPtr(true),
   136  						},
   137  						Migrate: DefaultMigrateStrategy(),
   138  						Tasks: []*Task{
   139  							{
   140  								KillTimeout: helper.TimeToPtr(5 * time.Second),
   141  								LogConfig:   DefaultLogConfig(),
   142  								Resources:   DefaultResources(),
   143  							},
   144  						},
   145  					},
   146  				},
   147  			},
   148  		},
   149  		{
   150  			name: "partial",
   151  			input: &Job{
   152  				Name:      helper.StringToPtr("foo"),
   153  				Namespace: helper.StringToPtr("bar"),
   154  				ID:        helper.StringToPtr("bar"),
   155  				ParentID:  helper.StringToPtr("lol"),
   156  				TaskGroups: []*TaskGroup{
   157  					{
   158  						Name: helper.StringToPtr("bar"),
   159  						Tasks: []*Task{
   160  							{
   161  								Name: "task1",
   162  							},
   163  						},
   164  					},
   165  				},
   166  			},
   167  			expected: &Job{
   168  				Namespace:         helper.StringToPtr("bar"),
   169  				ID:                helper.StringToPtr("bar"),
   170  				Name:              helper.StringToPtr("foo"),
   171  				Region:            helper.StringToPtr("global"),
   172  				Type:              helper.StringToPtr("service"),
   173  				ParentID:          helper.StringToPtr("lol"),
   174  				Priority:          helper.IntToPtr(50),
   175  				AllAtOnce:         helper.BoolToPtr(false),
   176  				VaultToken:        helper.StringToPtr(""),
   177  				Stop:              helper.BoolToPtr(false),
   178  				Stable:            helper.BoolToPtr(false),
   179  				Version:           helper.Uint64ToPtr(0),
   180  				Status:            helper.StringToPtr(""),
   181  				StatusDescription: helper.StringToPtr(""),
   182  				CreateIndex:       helper.Uint64ToPtr(0),
   183  				ModifyIndex:       helper.Uint64ToPtr(0),
   184  				JobModifyIndex:    helper.Uint64ToPtr(0),
   185  				TaskGroups: []*TaskGroup{
   186  					{
   187  						Name:  helper.StringToPtr("bar"),
   188  						Count: helper.IntToPtr(1),
   189  						EphemeralDisk: &EphemeralDisk{
   190  							Sticky:  helper.BoolToPtr(false),
   191  							Migrate: helper.BoolToPtr(false),
   192  							SizeMB:  helper.IntToPtr(300),
   193  						},
   194  						RestartPolicy: &RestartPolicy{
   195  							Delay:    helper.TimeToPtr(15 * time.Second),
   196  							Attempts: helper.IntToPtr(2),
   197  							Interval: helper.TimeToPtr(30 * time.Minute),
   198  							Mode:     helper.StringToPtr("fail"),
   199  						},
   200  						ReschedulePolicy: &ReschedulePolicy{
   201  							Attempts:      helper.IntToPtr(0),
   202  							Interval:      helper.TimeToPtr(0),
   203  							DelayFunction: helper.StringToPtr("exponential"),
   204  							Delay:         helper.TimeToPtr(30 * time.Second),
   205  							MaxDelay:      helper.TimeToPtr(1 * time.Hour),
   206  							Unlimited:     helper.BoolToPtr(true),
   207  						},
   208  						Migrate: DefaultMigrateStrategy(),
   209  						Tasks: []*Task{
   210  							{
   211  								Name:        "task1",
   212  								LogConfig:   DefaultLogConfig(),
   213  								Resources:   DefaultResources(),
   214  								KillTimeout: helper.TimeToPtr(5 * time.Second),
   215  							},
   216  						},
   217  					},
   218  				},
   219  			},
   220  		},
   221  		{
   222  			name: "example_template",
   223  			input: &Job{
   224  				ID:          helper.StringToPtr("example_template"),
   225  				Name:        helper.StringToPtr("example_template"),
   226  				Datacenters: []string{"dc1"},
   227  				Type:        helper.StringToPtr("service"),
   228  				Update: &UpdateStrategy{
   229  					MaxParallel: helper.IntToPtr(1),
   230  				},
   231  				TaskGroups: []*TaskGroup{
   232  					{
   233  						Name:  helper.StringToPtr("cache"),
   234  						Count: helper.IntToPtr(1),
   235  						RestartPolicy: &RestartPolicy{
   236  							Interval: helper.TimeToPtr(5 * time.Minute),
   237  							Attempts: helper.IntToPtr(10),
   238  							Delay:    helper.TimeToPtr(25 * time.Second),
   239  							Mode:     helper.StringToPtr("delay"),
   240  						},
   241  						EphemeralDisk: &EphemeralDisk{
   242  							SizeMB: helper.IntToPtr(300),
   243  						},
   244  						Tasks: []*Task{
   245  							{
   246  								Name:   "redis",
   247  								Driver: "docker",
   248  								Config: map[string]interface{}{
   249  									"image": "redis:3.2",
   250  									"port_map": []map[string]int{{
   251  										"db": 6379,
   252  									}},
   253  								},
   254  								Resources: &Resources{
   255  									CPU:      helper.IntToPtr(500),
   256  									MemoryMB: helper.IntToPtr(256),
   257  									Networks: []*NetworkResource{
   258  										{
   259  											MBits: helper.IntToPtr(10),
   260  											DynamicPorts: []Port{
   261  												{
   262  													Label: "db",
   263  												},
   264  											},
   265  										},
   266  									},
   267  								},
   268  								Services: []*Service{
   269  									{
   270  										Name:      "redis-cache",
   271  										Tags:      []string{"global", "cache"},
   272  										PortLabel: "db",
   273  										Checks: []ServiceCheck{
   274  											{
   275  												Name:     "alive",
   276  												Type:     "tcp",
   277  												Interval: 10 * time.Second,
   278  												Timeout:  2 * time.Second,
   279  											},
   280  										},
   281  									},
   282  								},
   283  								Templates: []*Template{
   284  									{
   285  										EmbeddedTmpl: helper.StringToPtr("---"),
   286  										DestPath:     helper.StringToPtr("local/file.yml"),
   287  									},
   288  									{
   289  										EmbeddedTmpl: helper.StringToPtr("FOO=bar\n"),
   290  										DestPath:     helper.StringToPtr("local/file.env"),
   291  										Envvars:      helper.BoolToPtr(true),
   292  										VaultGrace:   helper.TimeToPtr(3 * time.Second),
   293  									},
   294  								},
   295  							},
   296  						},
   297  					},
   298  				},
   299  			},
   300  			expected: &Job{
   301  				Namespace:         helper.StringToPtr(DefaultNamespace),
   302  				ID:                helper.StringToPtr("example_template"),
   303  				Name:              helper.StringToPtr("example_template"),
   304  				ParentID:          helper.StringToPtr(""),
   305  				Priority:          helper.IntToPtr(50),
   306  				Region:            helper.StringToPtr("global"),
   307  				Type:              helper.StringToPtr("service"),
   308  				AllAtOnce:         helper.BoolToPtr(false),
   309  				VaultToken:        helper.StringToPtr(""),
   310  				Stop:              helper.BoolToPtr(false),
   311  				Stable:            helper.BoolToPtr(false),
   312  				Version:           helper.Uint64ToPtr(0),
   313  				Status:            helper.StringToPtr(""),
   314  				StatusDescription: helper.StringToPtr(""),
   315  				CreateIndex:       helper.Uint64ToPtr(0),
   316  				ModifyIndex:       helper.Uint64ToPtr(0),
   317  				JobModifyIndex:    helper.Uint64ToPtr(0),
   318  				Datacenters:       []string{"dc1"},
   319  				Update: &UpdateStrategy{
   320  					Stagger:         helper.TimeToPtr(30 * time.Second),
   321  					MaxParallel:     helper.IntToPtr(1),
   322  					HealthCheck:     helper.StringToPtr("checks"),
   323  					MinHealthyTime:  helper.TimeToPtr(10 * time.Second),
   324  					HealthyDeadline: helper.TimeToPtr(5 * time.Minute),
   325  					AutoRevert:      helper.BoolToPtr(false),
   326  					Canary:          helper.IntToPtr(0),
   327  				},
   328  				TaskGroups: []*TaskGroup{
   329  					{
   330  						Name:  helper.StringToPtr("cache"),
   331  						Count: helper.IntToPtr(1),
   332  						RestartPolicy: &RestartPolicy{
   333  							Interval: helper.TimeToPtr(5 * time.Minute),
   334  							Attempts: helper.IntToPtr(10),
   335  							Delay:    helper.TimeToPtr(25 * time.Second),
   336  							Mode:     helper.StringToPtr("delay"),
   337  						},
   338  						ReschedulePolicy: &ReschedulePolicy{
   339  							Attempts:      helper.IntToPtr(0),
   340  							Interval:      helper.TimeToPtr(0),
   341  							DelayFunction: helper.StringToPtr("exponential"),
   342  							Delay:         helper.TimeToPtr(30 * time.Second),
   343  							MaxDelay:      helper.TimeToPtr(1 * time.Hour),
   344  							Unlimited:     helper.BoolToPtr(true),
   345  						},
   346  						EphemeralDisk: &EphemeralDisk{
   347  							Sticky:  helper.BoolToPtr(false),
   348  							Migrate: helper.BoolToPtr(false),
   349  							SizeMB:  helper.IntToPtr(300),
   350  						},
   351  
   352  						Update: &UpdateStrategy{
   353  							Stagger:         helper.TimeToPtr(30 * time.Second),
   354  							MaxParallel:     helper.IntToPtr(1),
   355  							HealthCheck:     helper.StringToPtr("checks"),
   356  							MinHealthyTime:  helper.TimeToPtr(10 * time.Second),
   357  							HealthyDeadline: helper.TimeToPtr(5 * time.Minute),
   358  							AutoRevert:      helper.BoolToPtr(false),
   359  							Canary:          helper.IntToPtr(0),
   360  						},
   361  						Migrate: DefaultMigrateStrategy(),
   362  						Tasks: []*Task{
   363  							{
   364  								Name:   "redis",
   365  								Driver: "docker",
   366  								Config: map[string]interface{}{
   367  									"image": "redis:3.2",
   368  									"port_map": []map[string]int{{
   369  										"db": 6379,
   370  									}},
   371  								},
   372  								Resources: &Resources{
   373  									CPU:      helper.IntToPtr(500),
   374  									MemoryMB: helper.IntToPtr(256),
   375  									IOPS:     helper.IntToPtr(0),
   376  									Networks: []*NetworkResource{
   377  										{
   378  											MBits: helper.IntToPtr(10),
   379  											DynamicPorts: []Port{
   380  												{
   381  													Label: "db",
   382  												},
   383  											},
   384  										},
   385  									},
   386  								},
   387  								Services: []*Service{
   388  									{
   389  										Name:        "redis-cache",
   390  										Tags:        []string{"global", "cache"},
   391  										PortLabel:   "db",
   392  										AddressMode: "auto",
   393  										Checks: []ServiceCheck{
   394  											{
   395  												Name:     "alive",
   396  												Type:     "tcp",
   397  												Interval: 10 * time.Second,
   398  												Timeout:  2 * time.Second,
   399  											},
   400  										},
   401  									},
   402  								},
   403  								KillTimeout: helper.TimeToPtr(5 * time.Second),
   404  								LogConfig:   DefaultLogConfig(),
   405  								Templates: []*Template{
   406  									{
   407  										SourcePath:   helper.StringToPtr(""),
   408  										DestPath:     helper.StringToPtr("local/file.yml"),
   409  										EmbeddedTmpl: helper.StringToPtr("---"),
   410  										ChangeMode:   helper.StringToPtr("restart"),
   411  										ChangeSignal: helper.StringToPtr(""),
   412  										Splay:        helper.TimeToPtr(5 * time.Second),
   413  										Perms:        helper.StringToPtr("0644"),
   414  										LeftDelim:    helper.StringToPtr("{{"),
   415  										RightDelim:   helper.StringToPtr("}}"),
   416  										Envvars:      helper.BoolToPtr(false),
   417  										VaultGrace:   helper.TimeToPtr(15 * time.Second),
   418  									},
   419  									{
   420  										SourcePath:   helper.StringToPtr(""),
   421  										DestPath:     helper.StringToPtr("local/file.env"),
   422  										EmbeddedTmpl: helper.StringToPtr("FOO=bar\n"),
   423  										ChangeMode:   helper.StringToPtr("restart"),
   424  										ChangeSignal: helper.StringToPtr(""),
   425  										Splay:        helper.TimeToPtr(5 * time.Second),
   426  										Perms:        helper.StringToPtr("0644"),
   427  										LeftDelim:    helper.StringToPtr("{{"),
   428  										RightDelim:   helper.StringToPtr("}}"),
   429  										Envvars:      helper.BoolToPtr(true),
   430  										VaultGrace:   helper.TimeToPtr(3 * time.Second),
   431  									},
   432  								},
   433  							},
   434  						},
   435  					},
   436  				},
   437  			},
   438  		},
   439  
   440  		{
   441  			name: "periodic",
   442  			input: &Job{
   443  				ID:       helper.StringToPtr("bar"),
   444  				Periodic: &PeriodicConfig{},
   445  			},
   446  			expected: &Job{
   447  				Namespace:         helper.StringToPtr(DefaultNamespace),
   448  				ID:                helper.StringToPtr("bar"),
   449  				ParentID:          helper.StringToPtr(""),
   450  				Name:              helper.StringToPtr("bar"),
   451  				Region:            helper.StringToPtr("global"),
   452  				Type:              helper.StringToPtr("service"),
   453  				Priority:          helper.IntToPtr(50),
   454  				AllAtOnce:         helper.BoolToPtr(false),
   455  				VaultToken:        helper.StringToPtr(""),
   456  				Stop:              helper.BoolToPtr(false),
   457  				Stable:            helper.BoolToPtr(false),
   458  				Version:           helper.Uint64ToPtr(0),
   459  				Status:            helper.StringToPtr(""),
   460  				StatusDescription: helper.StringToPtr(""),
   461  				CreateIndex:       helper.Uint64ToPtr(0),
   462  				ModifyIndex:       helper.Uint64ToPtr(0),
   463  				JobModifyIndex:    helper.Uint64ToPtr(0),
   464  				Periodic: &PeriodicConfig{
   465  					Enabled:         helper.BoolToPtr(true),
   466  					Spec:            helper.StringToPtr(""),
   467  					SpecType:        helper.StringToPtr(PeriodicSpecCron),
   468  					ProhibitOverlap: helper.BoolToPtr(false),
   469  					TimeZone:        helper.StringToPtr("UTC"),
   470  				},
   471  			},
   472  		},
   473  
   474  		{
   475  			name: "update_merge",
   476  			input: &Job{
   477  				Name:     helper.StringToPtr("foo"),
   478  				ID:       helper.StringToPtr("bar"),
   479  				ParentID: helper.StringToPtr("lol"),
   480  				Update: &UpdateStrategy{
   481  					Stagger:         helper.TimeToPtr(1 * time.Second),
   482  					MaxParallel:     helper.IntToPtr(1),
   483  					HealthCheck:     helper.StringToPtr("checks"),
   484  					MinHealthyTime:  helper.TimeToPtr(10 * time.Second),
   485  					HealthyDeadline: helper.TimeToPtr(6 * time.Minute),
   486  					AutoRevert:      helper.BoolToPtr(false),
   487  					Canary:          helper.IntToPtr(0),
   488  				},
   489  				TaskGroups: []*TaskGroup{
   490  					{
   491  						Name: helper.StringToPtr("bar"),
   492  						Update: &UpdateStrategy{
   493  							Stagger:        helper.TimeToPtr(2 * time.Second),
   494  							MaxParallel:    helper.IntToPtr(2),
   495  							HealthCheck:    helper.StringToPtr("manual"),
   496  							MinHealthyTime: helper.TimeToPtr(1 * time.Second),
   497  							AutoRevert:     helper.BoolToPtr(true),
   498  							Canary:         helper.IntToPtr(1),
   499  						},
   500  						Tasks: []*Task{
   501  							{
   502  								Name: "task1",
   503  							},
   504  						},
   505  					},
   506  					{
   507  						Name: helper.StringToPtr("baz"),
   508  						Tasks: []*Task{
   509  							{
   510  								Name: "task1",
   511  							},
   512  						},
   513  					},
   514  				},
   515  			},
   516  			expected: &Job{
   517  				Namespace:         helper.StringToPtr(DefaultNamespace),
   518  				ID:                helper.StringToPtr("bar"),
   519  				Name:              helper.StringToPtr("foo"),
   520  				Region:            helper.StringToPtr("global"),
   521  				Type:              helper.StringToPtr("service"),
   522  				ParentID:          helper.StringToPtr("lol"),
   523  				Priority:          helper.IntToPtr(50),
   524  				AllAtOnce:         helper.BoolToPtr(false),
   525  				VaultToken:        helper.StringToPtr(""),
   526  				Stop:              helper.BoolToPtr(false),
   527  				Stable:            helper.BoolToPtr(false),
   528  				Version:           helper.Uint64ToPtr(0),
   529  				Status:            helper.StringToPtr(""),
   530  				StatusDescription: helper.StringToPtr(""),
   531  				CreateIndex:       helper.Uint64ToPtr(0),
   532  				ModifyIndex:       helper.Uint64ToPtr(0),
   533  				JobModifyIndex:    helper.Uint64ToPtr(0),
   534  				Update: &UpdateStrategy{
   535  					Stagger:         helper.TimeToPtr(1 * time.Second),
   536  					MaxParallel:     helper.IntToPtr(1),
   537  					HealthCheck:     helper.StringToPtr("checks"),
   538  					MinHealthyTime:  helper.TimeToPtr(10 * time.Second),
   539  					HealthyDeadline: helper.TimeToPtr(6 * time.Minute),
   540  					AutoRevert:      helper.BoolToPtr(false),
   541  					Canary:          helper.IntToPtr(0),
   542  				},
   543  				TaskGroups: []*TaskGroup{
   544  					{
   545  						Name:  helper.StringToPtr("bar"),
   546  						Count: helper.IntToPtr(1),
   547  						EphemeralDisk: &EphemeralDisk{
   548  							Sticky:  helper.BoolToPtr(false),
   549  							Migrate: helper.BoolToPtr(false),
   550  							SizeMB:  helper.IntToPtr(300),
   551  						},
   552  						RestartPolicy: &RestartPolicy{
   553  							Delay:    helper.TimeToPtr(15 * time.Second),
   554  							Attempts: helper.IntToPtr(2),
   555  							Interval: helper.TimeToPtr(30 * time.Minute),
   556  							Mode:     helper.StringToPtr("fail"),
   557  						},
   558  						ReschedulePolicy: &ReschedulePolicy{
   559  							Attempts:      helper.IntToPtr(0),
   560  							Interval:      helper.TimeToPtr(0),
   561  							DelayFunction: helper.StringToPtr("exponential"),
   562  							Delay:         helper.TimeToPtr(30 * time.Second),
   563  							MaxDelay:      helper.TimeToPtr(1 * time.Hour),
   564  							Unlimited:     helper.BoolToPtr(true),
   565  						},
   566  						Update: &UpdateStrategy{
   567  							Stagger:         helper.TimeToPtr(2 * time.Second),
   568  							MaxParallel:     helper.IntToPtr(2),
   569  							HealthCheck:     helper.StringToPtr("manual"),
   570  							MinHealthyTime:  helper.TimeToPtr(1 * time.Second),
   571  							HealthyDeadline: helper.TimeToPtr(6 * time.Minute),
   572  							AutoRevert:      helper.BoolToPtr(true),
   573  							Canary:          helper.IntToPtr(1),
   574  						},
   575  						Migrate: DefaultMigrateStrategy(),
   576  						Tasks: []*Task{
   577  							{
   578  								Name:        "task1",
   579  								LogConfig:   DefaultLogConfig(),
   580  								Resources:   DefaultResources(),
   581  								KillTimeout: helper.TimeToPtr(5 * time.Second),
   582  							},
   583  						},
   584  					},
   585  					{
   586  						Name:  helper.StringToPtr("baz"),
   587  						Count: helper.IntToPtr(1),
   588  						EphemeralDisk: &EphemeralDisk{
   589  							Sticky:  helper.BoolToPtr(false),
   590  							Migrate: helper.BoolToPtr(false),
   591  							SizeMB:  helper.IntToPtr(300),
   592  						},
   593  						RestartPolicy: &RestartPolicy{
   594  							Delay:    helper.TimeToPtr(15 * time.Second),
   595  							Attempts: helper.IntToPtr(2),
   596  							Interval: helper.TimeToPtr(30 * time.Minute),
   597  							Mode:     helper.StringToPtr("fail"),
   598  						},
   599  						ReschedulePolicy: &ReschedulePolicy{
   600  							Attempts:      helper.IntToPtr(0),
   601  							Interval:      helper.TimeToPtr(0),
   602  							DelayFunction: helper.StringToPtr("exponential"),
   603  							Delay:         helper.TimeToPtr(30 * time.Second),
   604  							MaxDelay:      helper.TimeToPtr(1 * time.Hour),
   605  							Unlimited:     helper.BoolToPtr(true),
   606  						},
   607  						Update: &UpdateStrategy{
   608  							Stagger:         helper.TimeToPtr(1 * time.Second),
   609  							MaxParallel:     helper.IntToPtr(1),
   610  							HealthCheck:     helper.StringToPtr("checks"),
   611  							MinHealthyTime:  helper.TimeToPtr(10 * time.Second),
   612  							HealthyDeadline: helper.TimeToPtr(6 * time.Minute),
   613  							AutoRevert:      helper.BoolToPtr(false),
   614  							Canary:          helper.IntToPtr(0),
   615  						},
   616  						Migrate: DefaultMigrateStrategy(),
   617  						Tasks: []*Task{
   618  							{
   619  								Name:        "task1",
   620  								LogConfig:   DefaultLogConfig(),
   621  								Resources:   DefaultResources(),
   622  								KillTimeout: helper.TimeToPtr(5 * time.Second),
   623  							},
   624  						},
   625  					},
   626  				},
   627  			},
   628  		},
   629  	}
   630  
   631  	for _, tc := range testCases {
   632  		t.Run(tc.name, func(t *testing.T) {
   633  			tc.input.Canonicalize()
   634  			if !reflect.DeepEqual(tc.input, tc.expected) {
   635  				t.Fatalf("Name: %v, Diffs:\n%v", tc.name, pretty.Diff(tc.expected, tc.input))
   636  			}
   637  		})
   638  	}
   639  }
   640  
   641  func TestJobs_EnforceRegister(t *testing.T) {
   642  	t.Parallel()
   643  	require := require.New(t)
   644  	c, s := makeClient(t, nil, nil)
   645  	defer s.Stop()
   646  	jobs := c.Jobs()
   647  
   648  	// Listing jobs before registering returns nothing
   649  	resp, _, err := jobs.List(nil)
   650  	require.Nil(err)
   651  	require.Empty(resp)
   652  
   653  	// Create a job and attempt to register it with an incorrect index.
   654  	job := testJob()
   655  	resp2, _, err := jobs.EnforceRegister(job, 10, nil)
   656  	require.NotNil(err)
   657  	require.Contains(err.Error(), RegisterEnforceIndexErrPrefix)
   658  
   659  	// Register
   660  	resp2, wm, err := jobs.EnforceRegister(job, 0, nil)
   661  	require.Nil(err)
   662  	require.NotNil(resp2)
   663  	require.NotZero(resp2.EvalID)
   664  	assertWriteMeta(t, wm)
   665  
   666  	// Query the jobs back out again
   667  	resp, qm, err := jobs.List(nil)
   668  	require.Nil(err)
   669  	require.Len(resp, 1)
   670  	require.Equal(*job.ID, resp[0].ID)
   671  	assertQueryMeta(t, qm)
   672  
   673  	// Fail at incorrect index
   674  	curIndex := resp[0].JobModifyIndex
   675  	resp2, _, err = jobs.EnforceRegister(job, 123456, nil)
   676  	require.NotNil(err)
   677  	require.Contains(err.Error(), RegisterEnforceIndexErrPrefix)
   678  
   679  	// Works at correct index
   680  	resp3, wm, err := jobs.EnforceRegister(job, curIndex, nil)
   681  	require.Nil(err)
   682  	require.NotNil(resp3)
   683  	require.NotZero(resp3.EvalID)
   684  	assertWriteMeta(t, wm)
   685  }
   686  
   687  func TestJobs_Revert(t *testing.T) {
   688  	t.Parallel()
   689  	c, s := makeClient(t, nil, nil)
   690  	defer s.Stop()
   691  	jobs := c.Jobs()
   692  
   693  	// Register twice
   694  	job := testJob()
   695  	resp, wm, err := jobs.Register(job, nil)
   696  	if err != nil {
   697  		t.Fatalf("err: %s", err)
   698  	}
   699  	if resp == nil || resp.EvalID == "" {
   700  		t.Fatalf("missing eval id")
   701  	}
   702  	assertWriteMeta(t, wm)
   703  
   704  	job.Meta = map[string]string{"foo": "new"}
   705  	resp, wm, err = jobs.Register(job, nil)
   706  	if err != nil {
   707  		t.Fatalf("err: %s", err)
   708  	}
   709  	if resp == nil || resp.EvalID == "" {
   710  		t.Fatalf("missing eval id")
   711  	}
   712  	assertWriteMeta(t, wm)
   713  
   714  	// Fail revert at incorrect enforce
   715  	_, _, err = jobs.Revert(*job.ID, 0, helper.Uint64ToPtr(10), nil)
   716  	if err == nil || !strings.Contains(err.Error(), "enforcing version") {
   717  		t.Fatalf("expected enforcement error: %v", err)
   718  	}
   719  
   720  	// Works at correct index
   721  	revertResp, wm, err := jobs.Revert(*job.ID, 0, helper.Uint64ToPtr(1), nil)
   722  	if err != nil {
   723  		t.Fatalf("err: %s", err)
   724  	}
   725  	if revertResp.EvalID == "" {
   726  		t.Fatalf("missing eval id")
   727  	}
   728  	if revertResp.EvalCreateIndex == 0 {
   729  		t.Fatalf("bad eval create index")
   730  	}
   731  	if revertResp.JobModifyIndex == 0 {
   732  		t.Fatalf("bad job modify index")
   733  	}
   734  	assertWriteMeta(t, wm)
   735  }
   736  
   737  func TestJobs_Info(t *testing.T) {
   738  	t.Parallel()
   739  	c, s := makeClient(t, nil, nil)
   740  	defer s.Stop()
   741  	jobs := c.Jobs()
   742  
   743  	// Trying to retrieve a job by ID before it exists
   744  	// returns an error
   745  	_, _, err := jobs.Info("job1", nil)
   746  	if err == nil || !strings.Contains(err.Error(), "not found") {
   747  		t.Fatalf("expected not found error, got: %#v", err)
   748  	}
   749  
   750  	// Register the job
   751  	job := testJob()
   752  	_, wm, err := jobs.Register(job, nil)
   753  	if err != nil {
   754  		t.Fatalf("err: %s", err)
   755  	}
   756  	assertWriteMeta(t, wm)
   757  
   758  	// Query the job again and ensure it exists
   759  	result, qm, err := jobs.Info("job1", nil)
   760  	if err != nil {
   761  		t.Fatalf("err: %s", err)
   762  	}
   763  	assertQueryMeta(t, qm)
   764  
   765  	// Check that the result is what we expect
   766  	if result == nil || *result.ID != *job.ID {
   767  		t.Fatalf("expect: %#v, got: %#v", job, result)
   768  	}
   769  }
   770  
   771  func TestJobs_Versions(t *testing.T) {
   772  	t.Parallel()
   773  	c, s := makeClient(t, nil, nil)
   774  	defer s.Stop()
   775  	jobs := c.Jobs()
   776  
   777  	// Trying to retrieve a job by ID before it exists returns an error
   778  	_, _, _, err := jobs.Versions("job1", false, nil)
   779  	if err == nil || !strings.Contains(err.Error(), "not found") {
   780  		t.Fatalf("expected not found error, got: %#v", err)
   781  	}
   782  
   783  	// Register the job
   784  	job := testJob()
   785  	_, wm, err := jobs.Register(job, nil)
   786  	if err != nil {
   787  		t.Fatalf("err: %s", err)
   788  	}
   789  	assertWriteMeta(t, wm)
   790  
   791  	// Query the job again and ensure it exists
   792  	result, _, qm, err := jobs.Versions("job1", false, nil)
   793  	if err != nil {
   794  		t.Fatalf("err: %s", err)
   795  	}
   796  	assertQueryMeta(t, qm)
   797  
   798  	// Check that the result is what we expect
   799  	if len(result) == 0 || *result[0].ID != *job.ID {
   800  		t.Fatalf("expect: %#v, got: %#v", job, result)
   801  	}
   802  }
   803  
   804  func TestJobs_PrefixList(t *testing.T) {
   805  	t.Parallel()
   806  	c, s := makeClient(t, nil, nil)
   807  	defer s.Stop()
   808  	jobs := c.Jobs()
   809  
   810  	// Listing when nothing exists returns empty
   811  	results, _, err := jobs.PrefixList("dummy")
   812  	if err != nil {
   813  		t.Fatalf("err: %s", err)
   814  	}
   815  	if n := len(results); n != 0 {
   816  		t.Fatalf("expected 0 jobs, got: %d", n)
   817  	}
   818  
   819  	// Register the job
   820  	job := testJob()
   821  	_, wm, err := jobs.Register(job, nil)
   822  	if err != nil {
   823  		t.Fatalf("err: %s", err)
   824  	}
   825  	assertWriteMeta(t, wm)
   826  
   827  	// Query the job again and ensure it exists
   828  	// Listing when nothing exists returns empty
   829  	results, _, err = jobs.PrefixList((*job.ID)[:1])
   830  	if err != nil {
   831  		t.Fatalf("err: %s", err)
   832  	}
   833  
   834  	// Check if we have the right list
   835  	if len(results) != 1 || results[0].ID != *job.ID {
   836  		t.Fatalf("bad: %#v", results)
   837  	}
   838  }
   839  
   840  func TestJobs_List(t *testing.T) {
   841  	t.Parallel()
   842  	c, s := makeClient(t, nil, nil)
   843  	defer s.Stop()
   844  	jobs := c.Jobs()
   845  
   846  	// Listing when nothing exists returns empty
   847  	results, _, err := jobs.List(nil)
   848  	if err != nil {
   849  		t.Fatalf("err: %s", err)
   850  	}
   851  	if n := len(results); n != 0 {
   852  		t.Fatalf("expected 0 jobs, got: %d", n)
   853  	}
   854  
   855  	// Register the job
   856  	job := testJob()
   857  	_, wm, err := jobs.Register(job, nil)
   858  	if err != nil {
   859  		t.Fatalf("err: %s", err)
   860  	}
   861  	assertWriteMeta(t, wm)
   862  
   863  	// Query the job again and ensure it exists
   864  	// Listing when nothing exists returns empty
   865  	results, _, err = jobs.List(nil)
   866  	if err != nil {
   867  		t.Fatalf("err: %s", err)
   868  	}
   869  
   870  	// Check if we have the right list
   871  	if len(results) != 1 || results[0].ID != *job.ID {
   872  		t.Fatalf("bad: %#v", results)
   873  	}
   874  }
   875  
   876  func TestJobs_Allocations(t *testing.T) {
   877  	t.Parallel()
   878  	c, s := makeClient(t, nil, nil)
   879  	defer s.Stop()
   880  	jobs := c.Jobs()
   881  
   882  	// Looking up by a nonexistent job returns nothing
   883  	allocs, qm, err := jobs.Allocations("job1", true, nil)
   884  	if err != nil {
   885  		t.Fatalf("err: %s", err)
   886  	}
   887  	if qm.LastIndex != 0 {
   888  		t.Fatalf("bad index: %d", qm.LastIndex)
   889  	}
   890  	if n := len(allocs); n != 0 {
   891  		t.Fatalf("expected 0 allocs, got: %d", n)
   892  	}
   893  
   894  	// TODO: do something here to create some allocations for
   895  	// an existing job, lookup again.
   896  }
   897  
   898  func TestJobs_Evaluations(t *testing.T) {
   899  	t.Parallel()
   900  	c, s := makeClient(t, nil, nil)
   901  	defer s.Stop()
   902  	jobs := c.Jobs()
   903  
   904  	// Looking up by a nonexistent job ID returns nothing
   905  	evals, qm, err := jobs.Evaluations("job1", nil)
   906  	if err != nil {
   907  		t.Fatalf("err: %s", err)
   908  	}
   909  	if qm.LastIndex != 0 {
   910  		t.Fatalf("bad index: %d", qm.LastIndex)
   911  	}
   912  	if n := len(evals); n != 0 {
   913  		t.Fatalf("expected 0 evals, got: %d", n)
   914  	}
   915  
   916  	// Insert a job. This also creates an evaluation so we should
   917  	// be able to query that out after.
   918  	job := testJob()
   919  	resp, wm, err := jobs.Register(job, nil)
   920  	if err != nil {
   921  		t.Fatalf("err: %s", err)
   922  	}
   923  	assertWriteMeta(t, wm)
   924  
   925  	// Look up the evaluations again.
   926  	evals, qm, err = jobs.Evaluations("job1", nil)
   927  	if err != nil {
   928  		t.Fatalf("err: %s", err)
   929  	}
   930  	assertQueryMeta(t, qm)
   931  
   932  	// Check that we got the evals back, evals are in order most recent to least recent
   933  	// so the last eval is the original registered eval
   934  	idx := len(evals) - 1
   935  	if n := len(evals); n == 0 || evals[idx].ID != resp.EvalID {
   936  		t.Fatalf("expected >= 1 eval (%s), got: %#v", resp.EvalID, evals[idx])
   937  	}
   938  }
   939  
   940  func TestJobs_Deregister(t *testing.T) {
   941  	t.Parallel()
   942  	c, s := makeClient(t, nil, nil)
   943  	defer s.Stop()
   944  	jobs := c.Jobs()
   945  
   946  	// Register a new job
   947  	job := testJob()
   948  	_, wm, err := jobs.Register(job, nil)
   949  	if err != nil {
   950  		t.Fatalf("err: %s", err)
   951  	}
   952  	assertWriteMeta(t, wm)
   953  
   954  	// Attempting delete on non-existing job returns an error
   955  	if _, _, err = jobs.Deregister("nope", false, nil); err != nil {
   956  		t.Fatalf("unexpected error deregistering job: %v", err)
   957  	}
   958  
   959  	// Do a soft deregister of an existing job
   960  	evalID, wm3, err := jobs.Deregister("job1", false, nil)
   961  	if err != nil {
   962  		t.Fatalf("err: %s", err)
   963  	}
   964  	assertWriteMeta(t, wm3)
   965  	if evalID == "" {
   966  		t.Fatalf("missing eval ID")
   967  	}
   968  
   969  	// Check that the job is still queryable
   970  	out, qm1, err := jobs.Info("job1", nil)
   971  	if err != nil {
   972  		t.Fatalf("err: %s", err)
   973  	}
   974  	assertQueryMeta(t, qm1)
   975  	if out == nil {
   976  		t.Fatalf("missing job")
   977  	}
   978  
   979  	// Do a purge deregister of an existing job
   980  	evalID, wm4, err := jobs.Deregister("job1", true, nil)
   981  	if err != nil {
   982  		t.Fatalf("err: %s", err)
   983  	}
   984  	assertWriteMeta(t, wm4)
   985  	if evalID == "" {
   986  		t.Fatalf("missing eval ID")
   987  	}
   988  
   989  	// Check that the job is really gone
   990  	result, qm, err := jobs.List(nil)
   991  	if err != nil {
   992  		t.Fatalf("err: %s", err)
   993  	}
   994  	assertQueryMeta(t, qm)
   995  	if n := len(result); n != 0 {
   996  		t.Fatalf("expected 0 jobs, got: %d", n)
   997  	}
   998  }
   999  
  1000  func TestJobs_ForceEvaluate(t *testing.T) {
  1001  	t.Parallel()
  1002  	c, s := makeClient(t, nil, nil)
  1003  	defer s.Stop()
  1004  	jobs := c.Jobs()
  1005  
  1006  	// Force-eval on a non-existent job fails
  1007  	_, _, err := jobs.ForceEvaluate("job1", nil)
  1008  	if err == nil || !strings.Contains(err.Error(), "not found") {
  1009  		t.Fatalf("expected not found error, got: %#v", err)
  1010  	}
  1011  
  1012  	// Create a new job
  1013  	_, wm, err := jobs.Register(testJob(), nil)
  1014  	if err != nil {
  1015  		t.Fatalf("err: %s", err)
  1016  	}
  1017  	assertWriteMeta(t, wm)
  1018  
  1019  	// Try force-eval again
  1020  	evalID, wm, err := jobs.ForceEvaluate("job1", nil)
  1021  	if err != nil {
  1022  		t.Fatalf("err: %s", err)
  1023  	}
  1024  	assertWriteMeta(t, wm)
  1025  
  1026  	// Retrieve the evals and see if we get a matching one
  1027  	evals, qm, err := jobs.Evaluations("job1", nil)
  1028  	if err != nil {
  1029  		t.Fatalf("err: %s", err)
  1030  	}
  1031  	assertQueryMeta(t, qm)
  1032  	for _, eval := range evals {
  1033  		if eval.ID == evalID {
  1034  			return
  1035  		}
  1036  	}
  1037  	t.Fatalf("evaluation %q missing", evalID)
  1038  }
  1039  
  1040  func TestJobs_PeriodicForce(t *testing.T) {
  1041  	t.Parallel()
  1042  	c, s := makeClient(t, nil, nil)
  1043  	defer s.Stop()
  1044  	jobs := c.Jobs()
  1045  
  1046  	// Force-eval on a nonexistent job fails
  1047  	_, _, err := jobs.PeriodicForce("job1", nil)
  1048  	if err == nil || !strings.Contains(err.Error(), "not found") {
  1049  		t.Fatalf("expected not found error, got: %#v", err)
  1050  	}
  1051  
  1052  	// Create a new job
  1053  	job := testPeriodicJob()
  1054  	_, _, err = jobs.Register(job, nil)
  1055  	if err != nil {
  1056  		t.Fatalf("err: %s", err)
  1057  	}
  1058  
  1059  	testutil.WaitForResult(func() (bool, error) {
  1060  		out, _, err := jobs.Info(*job.ID, nil)
  1061  		if err != nil || out == nil || *out.ID != *job.ID {
  1062  			return false, err
  1063  		}
  1064  		return true, nil
  1065  	}, func(err error) {
  1066  		t.Fatalf("err: %s", err)
  1067  	})
  1068  
  1069  	// Try force again
  1070  	evalID, wm, err := jobs.PeriodicForce(*job.ID, nil)
  1071  	if err != nil {
  1072  		t.Fatalf("err: %s", err)
  1073  	}
  1074  	assertWriteMeta(t, wm)
  1075  
  1076  	if evalID == "" {
  1077  		t.Fatalf("empty evalID")
  1078  	}
  1079  
  1080  	// Retrieve the eval
  1081  	evals := c.Evaluations()
  1082  	eval, qm, err := evals.Info(evalID, nil)
  1083  	if err != nil {
  1084  		t.Fatalf("err: %s", err)
  1085  	}
  1086  	assertQueryMeta(t, qm)
  1087  	if eval.ID == evalID {
  1088  		return
  1089  	}
  1090  	t.Fatalf("evaluation %q missing", evalID)
  1091  }
  1092  
  1093  func TestJobs_Plan(t *testing.T) {
  1094  	t.Parallel()
  1095  	c, s := makeClient(t, nil, nil)
  1096  	defer s.Stop()
  1097  	jobs := c.Jobs()
  1098  
  1099  	// Create a job and attempt to register it
  1100  	job := testJob()
  1101  	resp, wm, err := jobs.Register(job, nil)
  1102  	if err != nil {
  1103  		t.Fatalf("err: %s", err)
  1104  	}
  1105  	if resp == nil || resp.EvalID == "" {
  1106  		t.Fatalf("missing eval id")
  1107  	}
  1108  	assertWriteMeta(t, wm)
  1109  
  1110  	// Check that passing a nil job fails
  1111  	if _, _, err := jobs.Plan(nil, true, nil); err == nil {
  1112  		t.Fatalf("expect an error when job isn't provided")
  1113  	}
  1114  
  1115  	// Make a plan request
  1116  	planResp, wm, err := jobs.Plan(job, true, nil)
  1117  	if err != nil {
  1118  		t.Fatalf("err: %s", err)
  1119  	}
  1120  	if planResp == nil {
  1121  		t.Fatalf("nil response")
  1122  	}
  1123  
  1124  	if planResp.JobModifyIndex == 0 {
  1125  		t.Fatalf("bad JobModifyIndex value: %#v", planResp)
  1126  	}
  1127  	if planResp.Diff == nil {
  1128  		t.Fatalf("got nil diff: %#v", planResp)
  1129  	}
  1130  	if planResp.Annotations == nil {
  1131  		t.Fatalf("got nil annotations: %#v", planResp)
  1132  	}
  1133  	// Can make this assertion because there are no clients.
  1134  	if len(planResp.CreatedEvals) == 0 {
  1135  		t.Fatalf("got no CreatedEvals: %#v", planResp)
  1136  	}
  1137  	assertWriteMeta(t, wm)
  1138  
  1139  	// Make a plan request w/o the diff
  1140  	planResp, wm, err = jobs.Plan(job, false, nil)
  1141  	if err != nil {
  1142  		t.Fatalf("err: %s", err)
  1143  	}
  1144  	assertWriteMeta(t, wm)
  1145  
  1146  	if planResp == nil {
  1147  		t.Fatalf("nil response")
  1148  	}
  1149  
  1150  	if planResp.JobModifyIndex == 0 {
  1151  		t.Fatalf("bad JobModifyIndex value: %d", planResp.JobModifyIndex)
  1152  	}
  1153  	if planResp.Diff != nil {
  1154  		t.Fatalf("got non-nil diff: %#v", planResp)
  1155  	}
  1156  	if planResp.Annotations == nil {
  1157  		t.Fatalf("got nil annotations: %#v", planResp)
  1158  	}
  1159  	// Can make this assertion because there are no clients.
  1160  	if len(planResp.CreatedEvals) == 0 {
  1161  		t.Fatalf("got no CreatedEvals: %#v", planResp)
  1162  	}
  1163  }
  1164  
  1165  func TestJobs_JobSummary(t *testing.T) {
  1166  	t.Parallel()
  1167  	c, s := makeClient(t, nil, nil)
  1168  	defer s.Stop()
  1169  	jobs := c.Jobs()
  1170  
  1171  	// Trying to retrieve a job summary before the job exists
  1172  	// returns an error
  1173  	_, _, err := jobs.Summary("job1", nil)
  1174  	if err == nil || !strings.Contains(err.Error(), "not found") {
  1175  		t.Fatalf("expected not found error, got: %#v", err)
  1176  	}
  1177  
  1178  	// Register the job
  1179  	job := testJob()
  1180  	taskName := job.TaskGroups[0].Name
  1181  	_, wm, err := jobs.Register(job, nil)
  1182  	if err != nil {
  1183  		t.Fatalf("err: %s", err)
  1184  	}
  1185  	assertWriteMeta(t, wm)
  1186  
  1187  	// Query the job summary again and ensure it exists
  1188  	result, qm, err := jobs.Summary("job1", nil)
  1189  	if err != nil {
  1190  		t.Fatalf("err: %s", err)
  1191  	}
  1192  	assertQueryMeta(t, qm)
  1193  
  1194  	// Check that the result is what we expect
  1195  	if *job.ID != result.JobID {
  1196  		t.Fatalf("err: expected job id of %s saw %s", *job.ID, result.JobID)
  1197  	}
  1198  	if _, ok := result.Summary[*taskName]; !ok {
  1199  		t.Fatalf("err: unable to find %s key in job summary", *taskName)
  1200  	}
  1201  }
  1202  
  1203  func TestJobs_NewBatchJob(t *testing.T) {
  1204  	t.Parallel()
  1205  	job := NewBatchJob("job1", "myjob", "region1", 5)
  1206  	expect := &Job{
  1207  		Region:   helper.StringToPtr("region1"),
  1208  		ID:       helper.StringToPtr("job1"),
  1209  		Name:     helper.StringToPtr("myjob"),
  1210  		Type:     helper.StringToPtr(JobTypeBatch),
  1211  		Priority: helper.IntToPtr(5),
  1212  	}
  1213  	if !reflect.DeepEqual(job, expect) {
  1214  		t.Fatalf("expect: %#v, got: %#v", expect, job)
  1215  	}
  1216  }
  1217  
  1218  func TestJobs_NewServiceJob(t *testing.T) {
  1219  	t.Parallel()
  1220  	job := NewServiceJob("job1", "myjob", "region1", 5)
  1221  	expect := &Job{
  1222  		Region:   helper.StringToPtr("region1"),
  1223  		ID:       helper.StringToPtr("job1"),
  1224  		Name:     helper.StringToPtr("myjob"),
  1225  		Type:     helper.StringToPtr(JobTypeService),
  1226  		Priority: helper.IntToPtr(5),
  1227  	}
  1228  	if !reflect.DeepEqual(job, expect) {
  1229  		t.Fatalf("expect: %#v, got: %#v", expect, job)
  1230  	}
  1231  }
  1232  
  1233  func TestJobs_SetMeta(t *testing.T) {
  1234  	t.Parallel()
  1235  	job := &Job{Meta: nil}
  1236  
  1237  	// Initializes a nil map
  1238  	out := job.SetMeta("foo", "bar")
  1239  	if job.Meta == nil {
  1240  		t.Fatalf("should initialize metadata")
  1241  	}
  1242  
  1243  	// Check that the job was returned
  1244  	if job != out {
  1245  		t.Fatalf("expect: %#v, got: %#v", job, out)
  1246  	}
  1247  
  1248  	// Setting another pair is additive
  1249  	job.SetMeta("baz", "zip")
  1250  	expect := map[string]string{"foo": "bar", "baz": "zip"}
  1251  	if !reflect.DeepEqual(job.Meta, expect) {
  1252  		t.Fatalf("expect: %#v, got: %#v", expect, job.Meta)
  1253  	}
  1254  }
  1255  
  1256  func TestJobs_Constrain(t *testing.T) {
  1257  	t.Parallel()
  1258  	job := &Job{Constraints: nil}
  1259  
  1260  	// Create and add a constraint
  1261  	out := job.Constrain(NewConstraint("kernel.name", "=", "darwin"))
  1262  	if n := len(job.Constraints); n != 1 {
  1263  		t.Fatalf("expected 1 constraint, got: %d", n)
  1264  	}
  1265  
  1266  	// Check that the job was returned
  1267  	if job != out {
  1268  		t.Fatalf("expect: %#v, got: %#v", job, out)
  1269  	}
  1270  
  1271  	// Adding another constraint preserves the original
  1272  	job.Constrain(NewConstraint("memory.totalbytes", ">=", "128000000"))
  1273  	expect := []*Constraint{
  1274  		{
  1275  			LTarget: "kernel.name",
  1276  			RTarget: "darwin",
  1277  			Operand: "=",
  1278  		},
  1279  		{
  1280  			LTarget: "memory.totalbytes",
  1281  			RTarget: "128000000",
  1282  			Operand: ">=",
  1283  		},
  1284  	}
  1285  	if !reflect.DeepEqual(job.Constraints, expect) {
  1286  		t.Fatalf("expect: %#v, got: %#v", expect, job.Constraints)
  1287  	}
  1288  }
  1289  
  1290  func TestJobs_Sort(t *testing.T) {
  1291  	t.Parallel()
  1292  	jobs := []*JobListStub{
  1293  		{ID: "job2"},
  1294  		{ID: "job0"},
  1295  		{ID: "job1"},
  1296  	}
  1297  	sort.Sort(JobIDSort(jobs))
  1298  
  1299  	expect := []*JobListStub{
  1300  		{ID: "job0"},
  1301  		{ID: "job1"},
  1302  		{ID: "job2"},
  1303  	}
  1304  	if !reflect.DeepEqual(jobs, expect) {
  1305  		t.Fatalf("\n\n%#v\n\n%#v", jobs, expect)
  1306  	}
  1307  }
  1308  
  1309  func TestJobs_Summary_WithACL(t *testing.T) {
  1310  	t.Parallel()
  1311  	assert := assert.New(t)
  1312  
  1313  	c, s, root := makeACLClient(t, nil, nil)
  1314  	defer s.Stop()
  1315  	jobs := c.Jobs()
  1316  
  1317  	invalidToken := mock.ACLToken()
  1318  
  1319  	// Registering with an invalid  token should fail
  1320  	c.SetSecretID(invalidToken.SecretID)
  1321  	job := testJob()
  1322  	_, _, err := jobs.Register(job, nil)
  1323  	assert.NotNil(err)
  1324  
  1325  	// Register with token should succeed
  1326  	c.SetSecretID(root.SecretID)
  1327  	resp2, wm, err := jobs.Register(job, nil)
  1328  	assert.Nil(err)
  1329  	assert.NotNil(resp2)
  1330  	assert.NotEqual("", resp2.EvalID)
  1331  	assertWriteMeta(t, wm)
  1332  
  1333  	// Query the job summary with an invalid token should fail
  1334  	c.SetSecretID(invalidToken.SecretID)
  1335  	result, _, err := jobs.Summary(*job.ID, nil)
  1336  	assert.NotNil(err)
  1337  
  1338  	// Query the job summary with a valid token should succeed
  1339  	c.SetSecretID(root.SecretID)
  1340  	result, qm, err := jobs.Summary(*job.ID, nil)
  1341  	assert.Nil(err)
  1342  	assertQueryMeta(t, qm)
  1343  
  1344  	// Check that the result is what we expect
  1345  	assert.Equal(*job.ID, result.JobID)
  1346  }