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