github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/nomad/structs/structs_test.go (about)

     1  package structs
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"reflect"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/hashicorp/consul/api"
    12  	"github.com/hashicorp/go-multierror"
    13  	"github.com/hashicorp/nomad/helper/uuid"
    14  	"github.com/kr/pretty"
    15  	"github.com/stretchr/testify/assert"
    16  )
    17  
    18  func TestJob_Validate(t *testing.T) {
    19  	j := &Job{}
    20  	err := j.Validate()
    21  	mErr := err.(*multierror.Error)
    22  	if !strings.Contains(mErr.Errors[0].Error(), "job region") {
    23  		t.Fatalf("err: %s", err)
    24  	}
    25  	if !strings.Contains(mErr.Errors[1].Error(), "job ID") {
    26  		t.Fatalf("err: %s", err)
    27  	}
    28  	if !strings.Contains(mErr.Errors[2].Error(), "job name") {
    29  		t.Fatalf("err: %s", err)
    30  	}
    31  	if !strings.Contains(mErr.Errors[3].Error(), "namespace") {
    32  		t.Fatalf("err: %s", err)
    33  	}
    34  	if !strings.Contains(mErr.Errors[4].Error(), "job type") {
    35  		t.Fatalf("err: %s", err)
    36  	}
    37  	if !strings.Contains(mErr.Errors[5].Error(), "priority") {
    38  		t.Fatalf("err: %s", err)
    39  	}
    40  	if !strings.Contains(mErr.Errors[6].Error(), "datacenters") {
    41  		t.Fatalf("err: %s", err)
    42  	}
    43  	if !strings.Contains(mErr.Errors[7].Error(), "task groups") {
    44  		t.Fatalf("err: %s", err)
    45  	}
    46  
    47  	j = &Job{
    48  		Type: "invalid-job-type",
    49  	}
    50  	err = j.Validate()
    51  	if expected := `Invalid job type: "invalid-job-type"`; !strings.Contains(err.Error(), expected) {
    52  		t.Errorf("expected %s but found: %v", expected, err)
    53  	}
    54  
    55  	j = &Job{
    56  		Type: JobTypeService,
    57  		Periodic: &PeriodicConfig{
    58  			Enabled: true,
    59  		},
    60  	}
    61  	err = j.Validate()
    62  	mErr = err.(*multierror.Error)
    63  	if !strings.Contains(mErr.Error(), "Periodic") {
    64  		t.Fatalf("err: %s", err)
    65  	}
    66  
    67  	j = &Job{
    68  		Region:      "global",
    69  		ID:          uuid.Generate(),
    70  		Namespace:   "test",
    71  		Name:        "my-job",
    72  		Type:        JobTypeService,
    73  		Priority:    50,
    74  		Datacenters: []string{"dc1"},
    75  		TaskGroups: []*TaskGroup{
    76  			{
    77  				Name: "web",
    78  				RestartPolicy: &RestartPolicy{
    79  					Interval: 5 * time.Minute,
    80  					Delay:    10 * time.Second,
    81  					Attempts: 10,
    82  				},
    83  			},
    84  			{
    85  				Name: "web",
    86  				RestartPolicy: &RestartPolicy{
    87  					Interval: 5 * time.Minute,
    88  					Delay:    10 * time.Second,
    89  					Attempts: 10,
    90  				},
    91  			},
    92  			{
    93  				RestartPolicy: &RestartPolicy{
    94  					Interval: 5 * time.Minute,
    95  					Delay:    10 * time.Second,
    96  					Attempts: 10,
    97  				},
    98  			},
    99  		},
   100  	}
   101  	err = j.Validate()
   102  	mErr = err.(*multierror.Error)
   103  	if !strings.Contains(mErr.Errors[0].Error(), "2 redefines 'web' from group 1") {
   104  		t.Fatalf("err: %s", err)
   105  	}
   106  	if !strings.Contains(mErr.Errors[1].Error(), "group 3 missing name") {
   107  		t.Fatalf("err: %s", err)
   108  	}
   109  	if !strings.Contains(mErr.Errors[2].Error(), "Task group web validation failed") {
   110  		t.Fatalf("err: %s", err)
   111  	}
   112  }
   113  
   114  func TestJob_Warnings(t *testing.T) {
   115  	cases := []struct {
   116  		Name     string
   117  		Job      *Job
   118  		Expected []string
   119  	}{
   120  		{
   121  			Name:     "Higher counts for update stanza",
   122  			Expected: []string{"max parallel count is greater"},
   123  			Job: &Job{
   124  				Type: JobTypeService,
   125  				TaskGroups: []*TaskGroup{
   126  					{
   127  						Name:  "foo",
   128  						Count: 2,
   129  						Update: &UpdateStrategy{
   130  							MaxParallel: 10,
   131  						},
   132  					},
   133  				},
   134  			},
   135  		},
   136  	}
   137  
   138  	for _, c := range cases {
   139  		t.Run(c.Name, func(t *testing.T) {
   140  			warnings := c.Job.Warnings()
   141  			if warnings == nil {
   142  				if len(c.Expected) == 0 {
   143  					return
   144  				} else {
   145  					t.Fatal("Got no warnings when they were expected")
   146  				}
   147  			}
   148  
   149  			a := warnings.Error()
   150  			for _, e := range c.Expected {
   151  				if !strings.Contains(a, e) {
   152  					t.Fatalf("Got warnings %q; didn't contain %q", a, e)
   153  				}
   154  			}
   155  		})
   156  	}
   157  }
   158  
   159  func TestJob_Canonicalize_Update(t *testing.T) {
   160  	cases := []struct {
   161  		Name     string
   162  		Job      *Job
   163  		Expected *Job
   164  		Warnings []string
   165  	}{
   166  		{
   167  			Name:     "One task group",
   168  			Warnings: []string{"conversion to new update stanza"},
   169  			Job: &Job{
   170  				Namespace: "test",
   171  				Type:      JobTypeService,
   172  				Update: UpdateStrategy{
   173  					MaxParallel: 2,
   174  					Stagger:     10 * time.Second,
   175  				},
   176  				TaskGroups: []*TaskGroup{
   177  					{
   178  						Name:  "foo",
   179  						Count: 2,
   180  					},
   181  				},
   182  			},
   183  			Expected: &Job{
   184  				Namespace: "test",
   185  				Type:      JobTypeService,
   186  				Update: UpdateStrategy{
   187  					MaxParallel: 2,
   188  					Stagger:     10 * time.Second,
   189  				},
   190  				TaskGroups: []*TaskGroup{
   191  					{
   192  						Name:          "foo",
   193  						Count:         2,
   194  						RestartPolicy: NewRestartPolicy(JobTypeService),
   195  						EphemeralDisk: DefaultEphemeralDisk(),
   196  						Update: &UpdateStrategy{
   197  							Stagger:         30 * time.Second,
   198  							MaxParallel:     2,
   199  							HealthCheck:     UpdateStrategyHealthCheck_Checks,
   200  							MinHealthyTime:  10 * time.Second,
   201  							HealthyDeadline: 5 * time.Minute,
   202  							AutoRevert:      false,
   203  							Canary:          0,
   204  						},
   205  					},
   206  				},
   207  			},
   208  		},
   209  		{
   210  			Name:     "One task group batch",
   211  			Warnings: []string{"Update stanza is disallowed for batch jobs"},
   212  			Job: &Job{
   213  				Namespace: "test",
   214  				Type:      JobTypeBatch,
   215  				Update: UpdateStrategy{
   216  					MaxParallel: 2,
   217  					Stagger:     10 * time.Second,
   218  				},
   219  				TaskGroups: []*TaskGroup{
   220  					{
   221  						Name:  "foo",
   222  						Count: 2,
   223  					},
   224  				},
   225  			},
   226  			Expected: &Job{
   227  				Namespace: "test",
   228  				Type:      JobTypeBatch,
   229  				Update:    UpdateStrategy{},
   230  				TaskGroups: []*TaskGroup{
   231  					{
   232  						Name:          "foo",
   233  						Count:         2,
   234  						RestartPolicy: NewRestartPolicy(JobTypeBatch),
   235  						EphemeralDisk: DefaultEphemeralDisk(),
   236  					},
   237  				},
   238  			},
   239  		},
   240  		{
   241  			Name:     "One task group batch - new spec",
   242  			Warnings: []string{"Update stanza is disallowed for batch jobs"},
   243  			Job: &Job{
   244  				Namespace: "test",
   245  				Type:      JobTypeBatch,
   246  				Update: UpdateStrategy{
   247  					Stagger:         2 * time.Second,
   248  					MaxParallel:     2,
   249  					Canary:          2,
   250  					MinHealthyTime:  2 * time.Second,
   251  					HealthyDeadline: 10 * time.Second,
   252  					HealthCheck:     UpdateStrategyHealthCheck_Checks,
   253  				},
   254  				TaskGroups: []*TaskGroup{
   255  					{
   256  						Name:  "foo",
   257  						Count: 2,
   258  						Update: &UpdateStrategy{
   259  							Stagger:         2 * time.Second,
   260  							MaxParallel:     2,
   261  							Canary:          2,
   262  							MinHealthyTime:  2 * time.Second,
   263  							HealthyDeadline: 10 * time.Second,
   264  							HealthCheck:     UpdateStrategyHealthCheck_Checks,
   265  						},
   266  					},
   267  				},
   268  			},
   269  			Expected: &Job{
   270  				Namespace: "test",
   271  				Type:      JobTypeBatch,
   272  				Update:    UpdateStrategy{},
   273  				TaskGroups: []*TaskGroup{
   274  					{
   275  						Name:          "foo",
   276  						Count:         2,
   277  						RestartPolicy: NewRestartPolicy(JobTypeBatch),
   278  						EphemeralDisk: DefaultEphemeralDisk(),
   279  					},
   280  				},
   281  			},
   282  		},
   283  		{
   284  			Name: "One task group service - new spec",
   285  			Job: &Job{
   286  				Namespace: "test",
   287  				Type:      JobTypeService,
   288  				Update: UpdateStrategy{
   289  					Stagger:         2 * time.Second,
   290  					MaxParallel:     2,
   291  					Canary:          2,
   292  					MinHealthyTime:  2 * time.Second,
   293  					HealthyDeadline: 10 * time.Second,
   294  					HealthCheck:     UpdateStrategyHealthCheck_Checks,
   295  				},
   296  				TaskGroups: []*TaskGroup{
   297  					{
   298  						Name:  "foo",
   299  						Count: 2,
   300  						Update: &UpdateStrategy{
   301  							Stagger:         2 * time.Second,
   302  							MaxParallel:     2,
   303  							Canary:          2,
   304  							MinHealthyTime:  2 * time.Second,
   305  							HealthyDeadline: 10 * time.Second,
   306  							HealthCheck:     UpdateStrategyHealthCheck_Checks,
   307  						},
   308  					},
   309  				},
   310  			},
   311  			Expected: &Job{
   312  				Namespace: "test",
   313  				Type:      JobTypeService,
   314  				Update: UpdateStrategy{
   315  					Stagger:         2 * time.Second,
   316  					MaxParallel:     2,
   317  					Canary:          2,
   318  					MinHealthyTime:  2 * time.Second,
   319  					HealthyDeadline: 10 * time.Second,
   320  					HealthCheck:     UpdateStrategyHealthCheck_Checks,
   321  				},
   322  				TaskGroups: []*TaskGroup{
   323  					{
   324  						Name:          "foo",
   325  						Count:         2,
   326  						RestartPolicy: NewRestartPolicy(JobTypeService),
   327  						EphemeralDisk: DefaultEphemeralDisk(),
   328  						Update: &UpdateStrategy{
   329  							Stagger:         2 * time.Second,
   330  							MaxParallel:     2,
   331  							Canary:          2,
   332  							MinHealthyTime:  2 * time.Second,
   333  							HealthyDeadline: 10 * time.Second,
   334  							HealthCheck:     UpdateStrategyHealthCheck_Checks,
   335  						},
   336  					},
   337  				},
   338  			},
   339  		},
   340  		{
   341  			Name:     "One task group; too high of parallelism",
   342  			Warnings: []string{"conversion to new update stanza"},
   343  			Job: &Job{
   344  				Namespace: "test",
   345  				Type:      JobTypeService,
   346  				Update: UpdateStrategy{
   347  					MaxParallel: 200,
   348  					Stagger:     10 * time.Second,
   349  				},
   350  				TaskGroups: []*TaskGroup{
   351  					{
   352  						Name:  "foo",
   353  						Count: 2,
   354  					},
   355  				},
   356  			},
   357  			Expected: &Job{
   358  				Namespace: "test",
   359  				Type:      JobTypeService,
   360  				Update: UpdateStrategy{
   361  					MaxParallel: 200,
   362  					Stagger:     10 * time.Second,
   363  				},
   364  				TaskGroups: []*TaskGroup{
   365  					{
   366  						Name:          "foo",
   367  						Count:         2,
   368  						RestartPolicy: NewRestartPolicy(JobTypeService),
   369  						EphemeralDisk: DefaultEphemeralDisk(),
   370  						Update: &UpdateStrategy{
   371  							Stagger:         30 * time.Second,
   372  							MaxParallel:     2,
   373  							HealthCheck:     UpdateStrategyHealthCheck_Checks,
   374  							MinHealthyTime:  10 * time.Second,
   375  							HealthyDeadline: 5 * time.Minute,
   376  							AutoRevert:      false,
   377  							Canary:          0,
   378  						},
   379  					},
   380  				},
   381  			},
   382  		},
   383  		{
   384  			Name:     "Multiple task group; rounding",
   385  			Warnings: []string{"conversion to new update stanza"},
   386  			Job: &Job{
   387  				Namespace: "test",
   388  				Type:      JobTypeService,
   389  				Update: UpdateStrategy{
   390  					MaxParallel: 2,
   391  					Stagger:     10 * time.Second,
   392  				},
   393  				TaskGroups: []*TaskGroup{
   394  					{
   395  						Name:  "foo",
   396  						Count: 2,
   397  					},
   398  					{
   399  						Name:  "bar",
   400  						Count: 14,
   401  					},
   402  					{
   403  						Name:  "foo",
   404  						Count: 26,
   405  					},
   406  				},
   407  			},
   408  			Expected: &Job{
   409  				Namespace: "test",
   410  				Type:      JobTypeService,
   411  				Update: UpdateStrategy{
   412  					MaxParallel: 2,
   413  					Stagger:     10 * time.Second,
   414  				},
   415  				TaskGroups: []*TaskGroup{
   416  					{
   417  						Name:          "foo",
   418  						Count:         2,
   419  						RestartPolicy: NewRestartPolicy(JobTypeService),
   420  						EphemeralDisk: DefaultEphemeralDisk(),
   421  						Update: &UpdateStrategy{
   422  							Stagger:         30 * time.Second,
   423  							MaxParallel:     1,
   424  							HealthCheck:     UpdateStrategyHealthCheck_Checks,
   425  							MinHealthyTime:  10 * time.Second,
   426  							HealthyDeadline: 5 * time.Minute,
   427  							AutoRevert:      false,
   428  							Canary:          0,
   429  						},
   430  					},
   431  					{
   432  						Name:          "bar",
   433  						Count:         14,
   434  						RestartPolicy: NewRestartPolicy(JobTypeService),
   435  						EphemeralDisk: DefaultEphemeralDisk(),
   436  						Update: &UpdateStrategy{
   437  							Stagger:         30 * time.Second,
   438  							MaxParallel:     1,
   439  							HealthCheck:     UpdateStrategyHealthCheck_Checks,
   440  							MinHealthyTime:  10 * time.Second,
   441  							HealthyDeadline: 5 * time.Minute,
   442  							AutoRevert:      false,
   443  							Canary:          0,
   444  						},
   445  					},
   446  					{
   447  						Name:          "foo",
   448  						Count:         26,
   449  						EphemeralDisk: DefaultEphemeralDisk(),
   450  						RestartPolicy: NewRestartPolicy(JobTypeService),
   451  						Update: &UpdateStrategy{
   452  							Stagger:         30 * time.Second,
   453  							MaxParallel:     3,
   454  							HealthCheck:     UpdateStrategyHealthCheck_Checks,
   455  							MinHealthyTime:  10 * time.Second,
   456  							HealthyDeadline: 5 * time.Minute,
   457  							AutoRevert:      false,
   458  							Canary:          0,
   459  						},
   460  					},
   461  				},
   462  			},
   463  		},
   464  	}
   465  
   466  	for _, c := range cases {
   467  		t.Run(c.Name, func(t *testing.T) {
   468  			warnings := c.Job.Canonicalize()
   469  			if !reflect.DeepEqual(c.Job, c.Expected) {
   470  				t.Fatalf("Diff %#v", pretty.Diff(c.Job, c.Expected))
   471  			}
   472  
   473  			wErr := ""
   474  			if warnings != nil {
   475  				wErr = warnings.Error()
   476  			}
   477  			for _, w := range c.Warnings {
   478  				if !strings.Contains(wErr, w) {
   479  					t.Fatalf("Wanted warning %q: got %q", w, wErr)
   480  				}
   481  			}
   482  
   483  			if len(c.Warnings) == 0 && warnings != nil {
   484  				t.Fatalf("Wanted no warnings: got %q", wErr)
   485  			}
   486  		})
   487  	}
   488  }
   489  
   490  func TestJob_SpecChanged(t *testing.T) {
   491  	// Get a base test job
   492  	base := testJob()
   493  
   494  	// Only modify the indexes/mutable state of the job
   495  	mutatedBase := base.Copy()
   496  	mutatedBase.Status = "foo"
   497  	mutatedBase.ModifyIndex = base.ModifyIndex + 100
   498  
   499  	// changed contains a spec change that should be detected
   500  	change := base.Copy()
   501  	change.Priority = 99
   502  
   503  	cases := []struct {
   504  		Name     string
   505  		Original *Job
   506  		New      *Job
   507  		Changed  bool
   508  	}{
   509  		{
   510  			Name:     "Same job except mutable indexes",
   511  			Changed:  false,
   512  			Original: base,
   513  			New:      mutatedBase,
   514  		},
   515  		{
   516  			Name:     "Different",
   517  			Changed:  true,
   518  			Original: base,
   519  			New:      change,
   520  		},
   521  	}
   522  
   523  	for _, c := range cases {
   524  		t.Run(c.Name, func(t *testing.T) {
   525  			if actual := c.Original.SpecChanged(c.New); actual != c.Changed {
   526  				t.Fatalf("SpecChanged() returned %v; want %v", actual, c.Changed)
   527  			}
   528  		})
   529  	}
   530  }
   531  
   532  func testJob() *Job {
   533  	return &Job{
   534  		Region:      "global",
   535  		ID:          uuid.Generate(),
   536  		Namespace:   "test",
   537  		Name:        "my-job",
   538  		Type:        JobTypeService,
   539  		Priority:    50,
   540  		AllAtOnce:   false,
   541  		Datacenters: []string{"dc1"},
   542  		Constraints: []*Constraint{
   543  			{
   544  				LTarget: "$attr.kernel.name",
   545  				RTarget: "linux",
   546  				Operand: "=",
   547  			},
   548  		},
   549  		Periodic: &PeriodicConfig{
   550  			Enabled: false,
   551  		},
   552  		TaskGroups: []*TaskGroup{
   553  			{
   554  				Name:          "web",
   555  				Count:         10,
   556  				EphemeralDisk: DefaultEphemeralDisk(),
   557  				RestartPolicy: &RestartPolicy{
   558  					Mode:     RestartPolicyModeFail,
   559  					Attempts: 3,
   560  					Interval: 10 * time.Minute,
   561  					Delay:    1 * time.Minute,
   562  				},
   563  				Tasks: []*Task{
   564  					{
   565  						Name:   "web",
   566  						Driver: "exec",
   567  						Config: map[string]interface{}{
   568  							"command": "/bin/date",
   569  						},
   570  						Env: map[string]string{
   571  							"FOO": "bar",
   572  						},
   573  						Artifacts: []*TaskArtifact{
   574  							{
   575  								GetterSource: "http://foo.com",
   576  							},
   577  						},
   578  						Services: []*Service{
   579  							{
   580  								Name:      "${TASK}-frontend",
   581  								PortLabel: "http",
   582  							},
   583  						},
   584  						Resources: &Resources{
   585  							CPU:      500,
   586  							MemoryMB: 256,
   587  							Networks: []*NetworkResource{
   588  								{
   589  									MBits:        50,
   590  									DynamicPorts: []Port{{Label: "http"}},
   591  								},
   592  							},
   593  						},
   594  						LogConfig: &LogConfig{
   595  							MaxFiles:      10,
   596  							MaxFileSizeMB: 1,
   597  						},
   598  					},
   599  				},
   600  				Meta: map[string]string{
   601  					"elb_check_type":     "http",
   602  					"elb_check_interval": "30s",
   603  					"elb_check_min":      "3",
   604  				},
   605  			},
   606  		},
   607  		Meta: map[string]string{
   608  			"owner": "armon",
   609  		},
   610  	}
   611  }
   612  
   613  func TestJob_Copy(t *testing.T) {
   614  	j := testJob()
   615  	c := j.Copy()
   616  	if !reflect.DeepEqual(j, c) {
   617  		t.Fatalf("Copy() returned an unequal Job; got %#v; want %#v", c, j)
   618  	}
   619  }
   620  
   621  func TestJob_IsPeriodic(t *testing.T) {
   622  	j := &Job{
   623  		Type: JobTypeService,
   624  		Periodic: &PeriodicConfig{
   625  			Enabled: true,
   626  		},
   627  	}
   628  	if !j.IsPeriodic() {
   629  		t.Fatalf("IsPeriodic() returned false on periodic job")
   630  	}
   631  
   632  	j = &Job{
   633  		Type: JobTypeService,
   634  	}
   635  	if j.IsPeriodic() {
   636  		t.Fatalf("IsPeriodic() returned true on non-periodic job")
   637  	}
   638  }
   639  
   640  func TestJob_IsPeriodicActive(t *testing.T) {
   641  	cases := []struct {
   642  		job    *Job
   643  		active bool
   644  	}{
   645  		{
   646  			job: &Job{
   647  				Type: JobTypeService,
   648  				Periodic: &PeriodicConfig{
   649  					Enabled: true,
   650  				},
   651  			},
   652  			active: true,
   653  		},
   654  		{
   655  			job: &Job{
   656  				Type: JobTypeService,
   657  				Periodic: &PeriodicConfig{
   658  					Enabled: false,
   659  				},
   660  			},
   661  			active: false,
   662  		},
   663  		{
   664  			job: &Job{
   665  				Type: JobTypeService,
   666  				Periodic: &PeriodicConfig{
   667  					Enabled: true,
   668  				},
   669  				Stop: true,
   670  			},
   671  			active: false,
   672  		},
   673  		{
   674  			job: &Job{
   675  				Type: JobTypeService,
   676  				Periodic: &PeriodicConfig{
   677  					Enabled: false,
   678  				},
   679  				ParameterizedJob: &ParameterizedJobConfig{},
   680  			},
   681  			active: false,
   682  		},
   683  	}
   684  
   685  	for i, c := range cases {
   686  		if act := c.job.IsPeriodicActive(); act != c.active {
   687  			t.Fatalf("case %d failed: got %v; want %v", i, act, c.active)
   688  		}
   689  	}
   690  }
   691  
   692  func TestJob_SystemJob_Validate(t *testing.T) {
   693  	j := testJob()
   694  	j.Type = JobTypeSystem
   695  	j.Canonicalize()
   696  
   697  	err := j.Validate()
   698  	if err == nil || !strings.Contains(err.Error(), "exceed") {
   699  		t.Fatalf("expect error due to count")
   700  	}
   701  
   702  	j.TaskGroups[0].Count = 0
   703  	if err := j.Validate(); err != nil {
   704  		t.Fatalf("unexpected err: %v", err)
   705  	}
   706  
   707  	j.TaskGroups[0].Count = 1
   708  	if err := j.Validate(); err != nil {
   709  		t.Fatalf("unexpected err: %v", err)
   710  	}
   711  }
   712  
   713  func TestJob_VaultPolicies(t *testing.T) {
   714  	j0 := &Job{}
   715  	e0 := make(map[string]map[string]*Vault, 0)
   716  
   717  	vj1 := &Vault{
   718  		Policies: []string{
   719  			"p1",
   720  			"p2",
   721  		},
   722  	}
   723  	vj2 := &Vault{
   724  		Policies: []string{
   725  			"p3",
   726  			"p4",
   727  		},
   728  	}
   729  	vj3 := &Vault{
   730  		Policies: []string{
   731  			"p5",
   732  		},
   733  	}
   734  	j1 := &Job{
   735  		TaskGroups: []*TaskGroup{
   736  			{
   737  				Name: "foo",
   738  				Tasks: []*Task{
   739  					{
   740  						Name: "t1",
   741  					},
   742  					{
   743  						Name:  "t2",
   744  						Vault: vj1,
   745  					},
   746  				},
   747  			},
   748  			{
   749  				Name: "bar",
   750  				Tasks: []*Task{
   751  					{
   752  						Name:  "t3",
   753  						Vault: vj2,
   754  					},
   755  					{
   756  						Name:  "t4",
   757  						Vault: vj3,
   758  					},
   759  				},
   760  			},
   761  		},
   762  	}
   763  
   764  	e1 := map[string]map[string]*Vault{
   765  		"foo": {
   766  			"t2": vj1,
   767  		},
   768  		"bar": {
   769  			"t3": vj2,
   770  			"t4": vj3,
   771  		},
   772  	}
   773  
   774  	cases := []struct {
   775  		Job      *Job
   776  		Expected map[string]map[string]*Vault
   777  	}{
   778  		{
   779  			Job:      j0,
   780  			Expected: e0,
   781  		},
   782  		{
   783  			Job:      j1,
   784  			Expected: e1,
   785  		},
   786  	}
   787  
   788  	for i, c := range cases {
   789  		got := c.Job.VaultPolicies()
   790  		if !reflect.DeepEqual(got, c.Expected) {
   791  			t.Fatalf("case %d: got %#v; want %#v", i+1, got, c.Expected)
   792  		}
   793  	}
   794  }
   795  
   796  func TestJob_RequiredSignals(t *testing.T) {
   797  	j0 := &Job{}
   798  	e0 := make(map[string]map[string][]string, 0)
   799  
   800  	vj1 := &Vault{
   801  		Policies:   []string{"p1"},
   802  		ChangeMode: VaultChangeModeNoop,
   803  	}
   804  	vj2 := &Vault{
   805  		Policies:     []string{"p1"},
   806  		ChangeMode:   VaultChangeModeSignal,
   807  		ChangeSignal: "SIGUSR1",
   808  	}
   809  	tj1 := &Template{
   810  		SourcePath: "foo",
   811  		DestPath:   "bar",
   812  		ChangeMode: TemplateChangeModeNoop,
   813  	}
   814  	tj2 := &Template{
   815  		SourcePath:   "foo",
   816  		DestPath:     "bar",
   817  		ChangeMode:   TemplateChangeModeSignal,
   818  		ChangeSignal: "SIGUSR2",
   819  	}
   820  	j1 := &Job{
   821  		TaskGroups: []*TaskGroup{
   822  			{
   823  				Name: "foo",
   824  				Tasks: []*Task{
   825  					{
   826  						Name: "t1",
   827  					},
   828  					{
   829  						Name:      "t2",
   830  						Vault:     vj2,
   831  						Templates: []*Template{tj2},
   832  					},
   833  				},
   834  			},
   835  			{
   836  				Name: "bar",
   837  				Tasks: []*Task{
   838  					{
   839  						Name:      "t3",
   840  						Vault:     vj1,
   841  						Templates: []*Template{tj1},
   842  					},
   843  					{
   844  						Name:  "t4",
   845  						Vault: vj2,
   846  					},
   847  				},
   848  			},
   849  		},
   850  	}
   851  
   852  	e1 := map[string]map[string][]string{
   853  		"foo": {
   854  			"t2": {"SIGUSR1", "SIGUSR2"},
   855  		},
   856  		"bar": {
   857  			"t4": {"SIGUSR1"},
   858  		},
   859  	}
   860  
   861  	j2 := &Job{
   862  		TaskGroups: []*TaskGroup{
   863  			{
   864  				Name: "foo",
   865  				Tasks: []*Task{
   866  					{
   867  						Name:       "t1",
   868  						KillSignal: "SIGQUIT",
   869  					},
   870  				},
   871  			},
   872  		},
   873  	}
   874  
   875  	e2 := map[string]map[string][]string{
   876  		"foo": {
   877  			"t1": {"SIGQUIT"},
   878  		},
   879  	}
   880  
   881  	cases := []struct {
   882  		Job      *Job
   883  		Expected map[string]map[string][]string
   884  	}{
   885  		{
   886  			Job:      j0,
   887  			Expected: e0,
   888  		},
   889  		{
   890  			Job:      j1,
   891  			Expected: e1,
   892  		},
   893  		{
   894  			Job:      j2,
   895  			Expected: e2,
   896  		},
   897  	}
   898  
   899  	for i, c := range cases {
   900  		got := c.Job.RequiredSignals()
   901  		if !reflect.DeepEqual(got, c.Expected) {
   902  			t.Fatalf("case %d: got %#v; want %#v", i+1, got, c.Expected)
   903  		}
   904  	}
   905  }
   906  
   907  func TestTaskGroup_Validate(t *testing.T) {
   908  	j := testJob()
   909  	tg := &TaskGroup{
   910  		Count: -1,
   911  		RestartPolicy: &RestartPolicy{
   912  			Interval: 5 * time.Minute,
   913  			Delay:    10 * time.Second,
   914  			Attempts: 10,
   915  			Mode:     RestartPolicyModeDelay,
   916  		},
   917  	}
   918  	err := tg.Validate(j)
   919  	mErr := err.(*multierror.Error)
   920  	if !strings.Contains(mErr.Errors[0].Error(), "group name") {
   921  		t.Fatalf("err: %s", err)
   922  	}
   923  	if !strings.Contains(mErr.Errors[1].Error(), "count can't be negative") {
   924  		t.Fatalf("err: %s", err)
   925  	}
   926  	if !strings.Contains(mErr.Errors[2].Error(), "Missing tasks") {
   927  		t.Fatalf("err: %s", err)
   928  	}
   929  
   930  	tg = &TaskGroup{
   931  		Tasks: []*Task{
   932  			{
   933  				Name: "task-a",
   934  				Resources: &Resources{
   935  					Networks: []*NetworkResource{
   936  						{
   937  							ReservedPorts: []Port{{Label: "foo", Value: 123}},
   938  						},
   939  					},
   940  				},
   941  			},
   942  			{
   943  				Name: "task-b",
   944  				Resources: &Resources{
   945  					Networks: []*NetworkResource{
   946  						{
   947  							ReservedPorts: []Port{{Label: "foo", Value: 123}},
   948  						},
   949  					},
   950  				},
   951  			},
   952  		},
   953  	}
   954  	err = tg.Validate(&Job{})
   955  	expected := `Static port 123 already reserved by task-a:foo`
   956  	if !strings.Contains(err.Error(), expected) {
   957  		t.Errorf("expected %s but found: %v", expected, err)
   958  	}
   959  
   960  	tg = &TaskGroup{
   961  		Tasks: []*Task{
   962  			{
   963  				Name: "task-a",
   964  				Resources: &Resources{
   965  					Networks: []*NetworkResource{
   966  						{
   967  							ReservedPorts: []Port{
   968  								{Label: "foo", Value: 123},
   969  								{Label: "bar", Value: 123},
   970  							},
   971  						},
   972  					},
   973  				},
   974  			},
   975  		},
   976  	}
   977  	err = tg.Validate(&Job{})
   978  	expected = `Static port 123 already reserved by task-a:foo`
   979  	if !strings.Contains(err.Error(), expected) {
   980  		t.Errorf("expected %s but found: %v", expected, err)
   981  	}
   982  
   983  	tg = &TaskGroup{
   984  		Name:  "web",
   985  		Count: 1,
   986  		Tasks: []*Task{
   987  			{Name: "web", Leader: true},
   988  			{Name: "web", Leader: true},
   989  			{},
   990  		},
   991  		RestartPolicy: &RestartPolicy{
   992  			Interval: 5 * time.Minute,
   993  			Delay:    10 * time.Second,
   994  			Attempts: 10,
   995  			Mode:     RestartPolicyModeDelay,
   996  		},
   997  	}
   998  
   999  	err = tg.Validate(j)
  1000  	mErr = err.(*multierror.Error)
  1001  	if !strings.Contains(mErr.Errors[0].Error(), "should have an ephemeral disk object") {
  1002  		t.Fatalf("err: %s", err)
  1003  	}
  1004  	if !strings.Contains(mErr.Errors[1].Error(), "2 redefines 'web' from task 1") {
  1005  		t.Fatalf("err: %s", err)
  1006  	}
  1007  	if !strings.Contains(mErr.Errors[2].Error(), "Task 3 missing name") {
  1008  		t.Fatalf("err: %s", err)
  1009  	}
  1010  	if !strings.Contains(mErr.Errors[3].Error(), "Only one task may be marked as leader") {
  1011  		t.Fatalf("err: %s", err)
  1012  	}
  1013  	if !strings.Contains(mErr.Errors[4].Error(), "Task web validation failed") {
  1014  		t.Fatalf("err: %s", err)
  1015  	}
  1016  
  1017  	// COMPAT: Enable in 0.7.0
  1018  	//j.Type = JobTypeBatch
  1019  	//err = tg.Validate(j)
  1020  	//if !strings.Contains(err.Error(), "does not allow update block") {
  1021  	//t.Fatalf("err: %s", err)
  1022  	//}
  1023  }
  1024  
  1025  func TestTask_Validate(t *testing.T) {
  1026  	task := &Task{}
  1027  	ephemeralDisk := DefaultEphemeralDisk()
  1028  	err := task.Validate(ephemeralDisk)
  1029  	mErr := err.(*multierror.Error)
  1030  	if !strings.Contains(mErr.Errors[0].Error(), "task name") {
  1031  		t.Fatalf("err: %s", err)
  1032  	}
  1033  	if !strings.Contains(mErr.Errors[1].Error(), "task driver") {
  1034  		t.Fatalf("err: %s", err)
  1035  	}
  1036  	if !strings.Contains(mErr.Errors[2].Error(), "task resources") {
  1037  		t.Fatalf("err: %s", err)
  1038  	}
  1039  
  1040  	task = &Task{Name: "web/foo"}
  1041  	err = task.Validate(ephemeralDisk)
  1042  	mErr = err.(*multierror.Error)
  1043  	if !strings.Contains(mErr.Errors[0].Error(), "slashes") {
  1044  		t.Fatalf("err: %s", err)
  1045  	}
  1046  
  1047  	task = &Task{
  1048  		Name:   "web",
  1049  		Driver: "docker",
  1050  		Resources: &Resources{
  1051  			CPU:      100,
  1052  			MemoryMB: 100,
  1053  			IOPS:     10,
  1054  		},
  1055  		LogConfig: DefaultLogConfig(),
  1056  	}
  1057  	ephemeralDisk.SizeMB = 200
  1058  	err = task.Validate(ephemeralDisk)
  1059  	if err != nil {
  1060  		t.Fatalf("err: %s", err)
  1061  	}
  1062  
  1063  	task.Constraints = append(task.Constraints,
  1064  		&Constraint{
  1065  			Operand: ConstraintDistinctHosts,
  1066  		},
  1067  		&Constraint{
  1068  			Operand: ConstraintDistinctProperty,
  1069  			LTarget: "${meta.rack}",
  1070  		})
  1071  
  1072  	err = task.Validate(ephemeralDisk)
  1073  	mErr = err.(*multierror.Error)
  1074  	if !strings.Contains(mErr.Errors[0].Error(), "task level: distinct_hosts") {
  1075  		t.Fatalf("err: %s", err)
  1076  	}
  1077  	if !strings.Contains(mErr.Errors[1].Error(), "task level: distinct_property") {
  1078  		t.Fatalf("err: %s", err)
  1079  	}
  1080  }
  1081  
  1082  func TestTask_Validate_Services(t *testing.T) {
  1083  	s1 := &Service{
  1084  		Name:      "service-name",
  1085  		PortLabel: "bar",
  1086  		Checks: []*ServiceCheck{
  1087  			{
  1088  				Name:     "check-name",
  1089  				Type:     ServiceCheckTCP,
  1090  				Interval: 0 * time.Second,
  1091  			},
  1092  			{
  1093  				Name:    "check-name",
  1094  				Type:    ServiceCheckTCP,
  1095  				Timeout: 2 * time.Second,
  1096  			},
  1097  			{
  1098  				Name:     "check-name",
  1099  				Type:     ServiceCheckTCP,
  1100  				Interval: 1 * time.Second,
  1101  			},
  1102  		},
  1103  	}
  1104  
  1105  	s2 := &Service{
  1106  		Name:      "service-name",
  1107  		PortLabel: "bar",
  1108  	}
  1109  
  1110  	s3 := &Service{
  1111  		Name:      "service-A",
  1112  		PortLabel: "a",
  1113  	}
  1114  	s4 := &Service{
  1115  		Name:      "service-A",
  1116  		PortLabel: "b",
  1117  	}
  1118  
  1119  	ephemeralDisk := DefaultEphemeralDisk()
  1120  	ephemeralDisk.SizeMB = 200
  1121  	task := &Task{
  1122  		Name:   "web",
  1123  		Driver: "docker",
  1124  		Resources: &Resources{
  1125  			CPU:      100,
  1126  			MemoryMB: 100,
  1127  			IOPS:     10,
  1128  		},
  1129  		Services: []*Service{s1, s2},
  1130  	}
  1131  
  1132  	task1 := &Task{
  1133  		Name:      "web",
  1134  		Driver:    "docker",
  1135  		Resources: DefaultResources(),
  1136  		Services:  []*Service{s3, s4},
  1137  		LogConfig: DefaultLogConfig(),
  1138  	}
  1139  	task1.Resources.Networks = []*NetworkResource{
  1140  		{
  1141  			MBits: 10,
  1142  			DynamicPorts: []Port{
  1143  				{
  1144  					Label: "a",
  1145  					Value: 1000,
  1146  				},
  1147  				{
  1148  					Label: "b",
  1149  					Value: 2000,
  1150  				},
  1151  			},
  1152  		},
  1153  	}
  1154  
  1155  	err := task.Validate(ephemeralDisk)
  1156  	if err == nil {
  1157  		t.Fatal("expected an error")
  1158  	}
  1159  
  1160  	if !strings.Contains(err.Error(), "service \"service-name\" is duplicate") {
  1161  		t.Fatalf("err: %v", err)
  1162  	}
  1163  
  1164  	if !strings.Contains(err.Error(), "check \"check-name\" is duplicate") {
  1165  		t.Fatalf("err: %v", err)
  1166  	}
  1167  
  1168  	if !strings.Contains(err.Error(), "missing required value interval") {
  1169  		t.Fatalf("err: %v", err)
  1170  	}
  1171  
  1172  	if !strings.Contains(err.Error(), "cannot be less than") {
  1173  		t.Fatalf("err: %v", err)
  1174  	}
  1175  
  1176  	if err = task1.Validate(ephemeralDisk); err != nil {
  1177  		t.Fatalf("err : %v", err)
  1178  	}
  1179  }
  1180  
  1181  func TestTask_Validate_Service_Check(t *testing.T) {
  1182  
  1183  	invalidCheck := ServiceCheck{
  1184  		Name:     "check-name",
  1185  		Command:  "/bin/true",
  1186  		Type:     ServiceCheckScript,
  1187  		Interval: 10 * time.Second,
  1188  	}
  1189  
  1190  	err := invalidCheck.validate()
  1191  	if err == nil || !strings.Contains(err.Error(), "Timeout cannot be less") {
  1192  		t.Fatalf("expected a timeout validation error but received: %q", err)
  1193  	}
  1194  
  1195  	check1 := ServiceCheck{
  1196  		Name:     "check-name",
  1197  		Type:     ServiceCheckTCP,
  1198  		Interval: 10 * time.Second,
  1199  		Timeout:  2 * time.Second,
  1200  	}
  1201  
  1202  	if err := check1.validate(); err != nil {
  1203  		t.Fatalf("err: %v", err)
  1204  	}
  1205  
  1206  	check1.InitialStatus = "foo"
  1207  	err = check1.validate()
  1208  	if err == nil {
  1209  		t.Fatal("Expected an error")
  1210  	}
  1211  
  1212  	if !strings.Contains(err.Error(), "invalid initial check state (foo)") {
  1213  		t.Fatalf("err: %v", err)
  1214  	}
  1215  
  1216  	check1.InitialStatus = api.HealthCritical
  1217  	err = check1.validate()
  1218  	if err != nil {
  1219  		t.Fatalf("err: %v", err)
  1220  	}
  1221  
  1222  	check1.InitialStatus = api.HealthPassing
  1223  	err = check1.validate()
  1224  	if err != nil {
  1225  		t.Fatalf("err: %v", err)
  1226  	}
  1227  
  1228  	check1.InitialStatus = ""
  1229  	err = check1.validate()
  1230  	if err != nil {
  1231  		t.Fatalf("err: %v", err)
  1232  	}
  1233  }
  1234  
  1235  // TestTask_Validate_Service_Check_AddressMode asserts that checks do not
  1236  // inherit address mode but do inherit ports.
  1237  func TestTask_Validate_Service_Check_AddressMode(t *testing.T) {
  1238  	getTask := func(s *Service) *Task {
  1239  		return &Task{
  1240  			Resources: &Resources{
  1241  				Networks: []*NetworkResource{
  1242  					{
  1243  						DynamicPorts: []Port{
  1244  							{
  1245  								Label: "http",
  1246  								Value: 9999,
  1247  							},
  1248  						},
  1249  					},
  1250  				},
  1251  			},
  1252  			Services: []*Service{s},
  1253  		}
  1254  	}
  1255  
  1256  	cases := []struct {
  1257  		Service     *Service
  1258  		ErrContains string
  1259  	}{
  1260  		{
  1261  			Service: &Service{
  1262  				Name:        "invalid-driver",
  1263  				PortLabel:   "80",
  1264  				AddressMode: "host",
  1265  			},
  1266  			ErrContains: `port label "80" referenced`,
  1267  		},
  1268  		{
  1269  			Service: &Service{
  1270  				Name:        "http-driver-fail-1",
  1271  				PortLabel:   "80",
  1272  				AddressMode: "driver",
  1273  				Checks: []*ServiceCheck{
  1274  					{
  1275  						Name:     "invalid-check-1",
  1276  						Type:     "tcp",
  1277  						Interval: time.Second,
  1278  						Timeout:  time.Second,
  1279  					},
  1280  				},
  1281  			},
  1282  			ErrContains: `check "invalid-check-1" cannot use a numeric port`,
  1283  		},
  1284  		{
  1285  			Service: &Service{
  1286  				Name:        "http-driver-fail-2",
  1287  				PortLabel:   "80",
  1288  				AddressMode: "driver",
  1289  				Checks: []*ServiceCheck{
  1290  					{
  1291  						Name:      "invalid-check-2",
  1292  						Type:      "tcp",
  1293  						PortLabel: "80",
  1294  						Interval:  time.Second,
  1295  						Timeout:   time.Second,
  1296  					},
  1297  				},
  1298  			},
  1299  			ErrContains: `check "invalid-check-2" cannot use a numeric port`,
  1300  		},
  1301  		{
  1302  			Service: &Service{
  1303  				Name:        "http-driver-fail-3",
  1304  				PortLabel:   "80",
  1305  				AddressMode: "driver",
  1306  				Checks: []*ServiceCheck{
  1307  					{
  1308  						Name:      "invalid-check-3",
  1309  						Type:      "tcp",
  1310  						PortLabel: "missing-port-label",
  1311  						Interval:  time.Second,
  1312  						Timeout:   time.Second,
  1313  					},
  1314  				},
  1315  			},
  1316  			ErrContains: `port label "missing-port-label" referenced`,
  1317  		},
  1318  		{
  1319  			Service: &Service{
  1320  				Name:        "http-driver-passes",
  1321  				PortLabel:   "80",
  1322  				AddressMode: "driver",
  1323  				Checks: []*ServiceCheck{
  1324  					{
  1325  						Name:     "valid-script-check",
  1326  						Type:     "script",
  1327  						Command:  "ok",
  1328  						Interval: time.Second,
  1329  						Timeout:  time.Second,
  1330  					},
  1331  					{
  1332  						Name:      "valid-host-check",
  1333  						Type:      "tcp",
  1334  						PortLabel: "http",
  1335  						Interval:  time.Second,
  1336  						Timeout:   time.Second,
  1337  					},
  1338  					{
  1339  						Name:        "valid-driver-check",
  1340  						Type:        "tcp",
  1341  						AddressMode: "driver",
  1342  						Interval:    time.Second,
  1343  						Timeout:     time.Second,
  1344  					},
  1345  				},
  1346  			},
  1347  		},
  1348  		{
  1349  			Service: &Service{
  1350  				Name: "empty-address-3673-passes-1",
  1351  				Checks: []*ServiceCheck{
  1352  					{
  1353  						Name:      "valid-port-label",
  1354  						Type:      "tcp",
  1355  						PortLabel: "http",
  1356  						Interval:  time.Second,
  1357  						Timeout:   time.Second,
  1358  					},
  1359  					{
  1360  						Name:     "empty-is-ok",
  1361  						Type:     "script",
  1362  						Command:  "ok",
  1363  						Interval: time.Second,
  1364  						Timeout:  time.Second,
  1365  					},
  1366  				},
  1367  			},
  1368  		},
  1369  		{
  1370  			Service: &Service{
  1371  				Name: "empty-address-3673-passes-2",
  1372  			},
  1373  		},
  1374  		{
  1375  			Service: &Service{
  1376  				Name: "empty-address-3673-fails",
  1377  				Checks: []*ServiceCheck{
  1378  					{
  1379  						Name:     "empty-is-not-ok",
  1380  						Type:     "tcp",
  1381  						Interval: time.Second,
  1382  						Timeout:  time.Second,
  1383  					},
  1384  				},
  1385  			},
  1386  			ErrContains: `invalid: check requires a port but neither check nor service`,
  1387  		},
  1388  	}
  1389  
  1390  	for _, tc := range cases {
  1391  		tc := tc
  1392  		task := getTask(tc.Service)
  1393  		t.Run(tc.Service.Name, func(t *testing.T) {
  1394  			err := validateServices(task)
  1395  			if err == nil && tc.ErrContains == "" {
  1396  				// Ok!
  1397  				return
  1398  			}
  1399  			if err == nil {
  1400  				t.Fatalf("no error returned. expected: %s", tc.ErrContains)
  1401  			}
  1402  			if !strings.Contains(err.Error(), tc.ErrContains) {
  1403  				t.Fatalf("expected %q but found: %v", tc.ErrContains, err)
  1404  			}
  1405  		})
  1406  	}
  1407  }
  1408  
  1409  func TestTask_Validate_Service_Check_CheckRestart(t *testing.T) {
  1410  	invalidCheckRestart := &CheckRestart{
  1411  		Limit: -1,
  1412  		Grace: -1,
  1413  	}
  1414  
  1415  	err := invalidCheckRestart.Validate()
  1416  	assert.NotNil(t, err, "invalidateCheckRestart.Validate()")
  1417  	assert.Len(t, err.(*multierror.Error).Errors, 2)
  1418  
  1419  	validCheckRestart := &CheckRestart{}
  1420  	assert.Nil(t, validCheckRestart.Validate())
  1421  
  1422  	validCheckRestart.Limit = 1
  1423  	validCheckRestart.Grace = 1
  1424  	assert.Nil(t, validCheckRestart.Validate())
  1425  }
  1426  
  1427  func TestTask_Validate_LogConfig(t *testing.T) {
  1428  	task := &Task{
  1429  		LogConfig: DefaultLogConfig(),
  1430  	}
  1431  	ephemeralDisk := &EphemeralDisk{
  1432  		SizeMB: 1,
  1433  	}
  1434  
  1435  	err := task.Validate(ephemeralDisk)
  1436  	mErr := err.(*multierror.Error)
  1437  	if !strings.Contains(mErr.Errors[3].Error(), "log storage") {
  1438  		t.Fatalf("err: %s", err)
  1439  	}
  1440  }
  1441  
  1442  func TestTask_Validate_Template(t *testing.T) {
  1443  
  1444  	bad := &Template{}
  1445  	task := &Task{
  1446  		Templates: []*Template{bad},
  1447  	}
  1448  	ephemeralDisk := &EphemeralDisk{
  1449  		SizeMB: 1,
  1450  	}
  1451  
  1452  	err := task.Validate(ephemeralDisk)
  1453  	if !strings.Contains(err.Error(), "Template 1 validation failed") {
  1454  		t.Fatalf("err: %s", err)
  1455  	}
  1456  
  1457  	// Have two templates that share the same destination
  1458  	good := &Template{
  1459  		SourcePath: "foo",
  1460  		DestPath:   "local/foo",
  1461  		ChangeMode: "noop",
  1462  	}
  1463  
  1464  	task.Templates = []*Template{good, good}
  1465  	err = task.Validate(ephemeralDisk)
  1466  	if !strings.Contains(err.Error(), "same destination as") {
  1467  		t.Fatalf("err: %s", err)
  1468  	}
  1469  
  1470  	// Env templates can't use signals
  1471  	task.Templates = []*Template{
  1472  		{
  1473  			Envvars:    true,
  1474  			ChangeMode: "signal",
  1475  		},
  1476  	}
  1477  
  1478  	err = task.Validate(ephemeralDisk)
  1479  	if err == nil {
  1480  		t.Fatalf("expected error from Template.Validate")
  1481  	}
  1482  	if expected := "cannot use signals"; !strings.Contains(err.Error(), expected) {
  1483  		t.Errorf("expected to find %q but found %v", expected, err)
  1484  	}
  1485  }
  1486  
  1487  func TestTemplate_Validate(t *testing.T) {
  1488  	cases := []struct {
  1489  		Tmpl         *Template
  1490  		Fail         bool
  1491  		ContainsErrs []string
  1492  	}{
  1493  		{
  1494  			Tmpl: &Template{},
  1495  			Fail: true,
  1496  			ContainsErrs: []string{
  1497  				"specify a source path",
  1498  				"specify a destination",
  1499  				TemplateChangeModeInvalidError.Error(),
  1500  			},
  1501  		},
  1502  		{
  1503  			Tmpl: &Template{
  1504  				Splay: -100,
  1505  			},
  1506  			Fail: true,
  1507  			ContainsErrs: []string{
  1508  				"positive splay",
  1509  			},
  1510  		},
  1511  		{
  1512  			Tmpl: &Template{
  1513  				ChangeMode: "foo",
  1514  			},
  1515  			Fail: true,
  1516  			ContainsErrs: []string{
  1517  				TemplateChangeModeInvalidError.Error(),
  1518  			},
  1519  		},
  1520  		{
  1521  			Tmpl: &Template{
  1522  				ChangeMode: "signal",
  1523  			},
  1524  			Fail: true,
  1525  			ContainsErrs: []string{
  1526  				"specify signal value",
  1527  			},
  1528  		},
  1529  		{
  1530  			Tmpl: &Template{
  1531  				SourcePath: "foo",
  1532  				DestPath:   "../../root",
  1533  				ChangeMode: "noop",
  1534  			},
  1535  			Fail: true,
  1536  			ContainsErrs: []string{
  1537  				"destination escapes",
  1538  			},
  1539  		},
  1540  		{
  1541  			Tmpl: &Template{
  1542  				SourcePath: "foo",
  1543  				DestPath:   "local/foo",
  1544  				ChangeMode: "noop",
  1545  			},
  1546  			Fail: false,
  1547  		},
  1548  		{
  1549  			Tmpl: &Template{
  1550  				SourcePath: "foo",
  1551  				DestPath:   "local/foo",
  1552  				ChangeMode: "noop",
  1553  				Perms:      "0444",
  1554  			},
  1555  			Fail: false,
  1556  		},
  1557  		{
  1558  			Tmpl: &Template{
  1559  				SourcePath: "foo",
  1560  				DestPath:   "local/foo",
  1561  				ChangeMode: "noop",
  1562  				Perms:      "zza",
  1563  			},
  1564  			Fail: true,
  1565  			ContainsErrs: []string{
  1566  				"as octal",
  1567  			},
  1568  		},
  1569  	}
  1570  
  1571  	for i, c := range cases {
  1572  		err := c.Tmpl.Validate()
  1573  		if err != nil {
  1574  			if !c.Fail {
  1575  				t.Fatalf("Case %d: shouldn't have failed: %v", i+1, err)
  1576  			}
  1577  
  1578  			e := err.Error()
  1579  			for _, exp := range c.ContainsErrs {
  1580  				if !strings.Contains(e, exp) {
  1581  					t.Fatalf("Cased %d: should have contained error %q: %q", i+1, exp, e)
  1582  				}
  1583  			}
  1584  		} else if c.Fail {
  1585  			t.Fatalf("Case %d: should have failed: %v", i+1, err)
  1586  		}
  1587  	}
  1588  }
  1589  
  1590  func TestConstraint_Validate(t *testing.T) {
  1591  	c := &Constraint{}
  1592  	err := c.Validate()
  1593  	mErr := err.(*multierror.Error)
  1594  	if !strings.Contains(mErr.Errors[0].Error(), "Missing constraint operand") {
  1595  		t.Fatalf("err: %s", err)
  1596  	}
  1597  
  1598  	c = &Constraint{
  1599  		LTarget: "$attr.kernel.name",
  1600  		RTarget: "linux",
  1601  		Operand: "=",
  1602  	}
  1603  	err = c.Validate()
  1604  	if err != nil {
  1605  		t.Fatalf("err: %v", err)
  1606  	}
  1607  
  1608  	// Perform additional regexp validation
  1609  	c.Operand = ConstraintRegex
  1610  	c.RTarget = "(foo"
  1611  	err = c.Validate()
  1612  	mErr = err.(*multierror.Error)
  1613  	if !strings.Contains(mErr.Errors[0].Error(), "missing closing") {
  1614  		t.Fatalf("err: %s", err)
  1615  	}
  1616  
  1617  	// Perform version validation
  1618  	c.Operand = ConstraintVersion
  1619  	c.RTarget = "~> foo"
  1620  	err = c.Validate()
  1621  	mErr = err.(*multierror.Error)
  1622  	if !strings.Contains(mErr.Errors[0].Error(), "Malformed constraint") {
  1623  		t.Fatalf("err: %s", err)
  1624  	}
  1625  
  1626  	// Perform distinct_property validation
  1627  	c.Operand = ConstraintDistinctProperty
  1628  	c.RTarget = "0"
  1629  	err = c.Validate()
  1630  	mErr = err.(*multierror.Error)
  1631  	if !strings.Contains(mErr.Errors[0].Error(), "count of 1 or greater") {
  1632  		t.Fatalf("err: %s", err)
  1633  	}
  1634  
  1635  	c.RTarget = "-1"
  1636  	err = c.Validate()
  1637  	mErr = err.(*multierror.Error)
  1638  	if !strings.Contains(mErr.Errors[0].Error(), "to uint64") {
  1639  		t.Fatalf("err: %s", err)
  1640  	}
  1641  
  1642  	// Perform distinct_hosts validation
  1643  	c.Operand = ConstraintDistinctHosts
  1644  	c.LTarget = ""
  1645  	c.RTarget = ""
  1646  	if err := c.Validate(); err != nil {
  1647  		t.Fatalf("expected valid constraint: %v", err)
  1648  	}
  1649  
  1650  	// Perform set_contains validation
  1651  	c.Operand = ConstraintSetContains
  1652  	c.RTarget = ""
  1653  	err = c.Validate()
  1654  	mErr = err.(*multierror.Error)
  1655  	if !strings.Contains(mErr.Errors[0].Error(), "requires an RTarget") {
  1656  		t.Fatalf("err: %s", err)
  1657  	}
  1658  
  1659  	// Perform LTarget validation
  1660  	c.Operand = ConstraintRegex
  1661  	c.RTarget = "foo"
  1662  	c.LTarget = ""
  1663  	err = c.Validate()
  1664  	mErr = err.(*multierror.Error)
  1665  	if !strings.Contains(mErr.Errors[0].Error(), "No LTarget") {
  1666  		t.Fatalf("err: %s", err)
  1667  	}
  1668  
  1669  	// Perform constraint type validation
  1670  	c.Operand = "foo"
  1671  	err = c.Validate()
  1672  	mErr = err.(*multierror.Error)
  1673  	if !strings.Contains(mErr.Errors[0].Error(), "Unknown constraint type") {
  1674  		t.Fatalf("err: %s", err)
  1675  	}
  1676  }
  1677  
  1678  func TestUpdateStrategy_Validate(t *testing.T) {
  1679  	u := &UpdateStrategy{
  1680  		MaxParallel:     0,
  1681  		HealthCheck:     "foo",
  1682  		MinHealthyTime:  -10,
  1683  		HealthyDeadline: -15,
  1684  		AutoRevert:      false,
  1685  		Canary:          -1,
  1686  	}
  1687  
  1688  	err := u.Validate()
  1689  	mErr := err.(*multierror.Error)
  1690  	if !strings.Contains(mErr.Errors[0].Error(), "Invalid health check given") {
  1691  		t.Fatalf("err: %s", err)
  1692  	}
  1693  	if !strings.Contains(mErr.Errors[1].Error(), "Max parallel can not be less than one") {
  1694  		t.Fatalf("err: %s", err)
  1695  	}
  1696  	if !strings.Contains(mErr.Errors[2].Error(), "Canary count can not be less than zero") {
  1697  		t.Fatalf("err: %s", err)
  1698  	}
  1699  	if !strings.Contains(mErr.Errors[3].Error(), "Minimum healthy time may not be less than zero") {
  1700  		t.Fatalf("err: %s", err)
  1701  	}
  1702  	if !strings.Contains(mErr.Errors[4].Error(), "Healthy deadline must be greater than zero") {
  1703  		t.Fatalf("err: %s", err)
  1704  	}
  1705  	if !strings.Contains(mErr.Errors[5].Error(), "Minimum healthy time must be less than healthy deadline") {
  1706  		t.Fatalf("err: %s", err)
  1707  	}
  1708  }
  1709  
  1710  func TestResource_NetIndex(t *testing.T) {
  1711  	r := &Resources{
  1712  		Networks: []*NetworkResource{
  1713  			{Device: "eth0"},
  1714  			{Device: "lo0"},
  1715  			{Device: ""},
  1716  		},
  1717  	}
  1718  	if idx := r.NetIndex(&NetworkResource{Device: "eth0"}); idx != 0 {
  1719  		t.Fatalf("Bad: %d", idx)
  1720  	}
  1721  	if idx := r.NetIndex(&NetworkResource{Device: "lo0"}); idx != 1 {
  1722  		t.Fatalf("Bad: %d", idx)
  1723  	}
  1724  	if idx := r.NetIndex(&NetworkResource{Device: "eth1"}); idx != -1 {
  1725  		t.Fatalf("Bad: %d", idx)
  1726  	}
  1727  }
  1728  
  1729  func TestResource_Superset(t *testing.T) {
  1730  	r1 := &Resources{
  1731  		CPU:      2000,
  1732  		MemoryMB: 2048,
  1733  		DiskMB:   10000,
  1734  		IOPS:     100,
  1735  	}
  1736  	r2 := &Resources{
  1737  		CPU:      2000,
  1738  		MemoryMB: 1024,
  1739  		DiskMB:   5000,
  1740  		IOPS:     50,
  1741  	}
  1742  
  1743  	if s, _ := r1.Superset(r1); !s {
  1744  		t.Fatalf("bad")
  1745  	}
  1746  	if s, _ := r1.Superset(r2); !s {
  1747  		t.Fatalf("bad")
  1748  	}
  1749  	if s, _ := r2.Superset(r1); s {
  1750  		t.Fatalf("bad")
  1751  	}
  1752  	if s, _ := r2.Superset(r2); !s {
  1753  		t.Fatalf("bad")
  1754  	}
  1755  }
  1756  
  1757  func TestResource_Add(t *testing.T) {
  1758  	r1 := &Resources{
  1759  		CPU:      2000,
  1760  		MemoryMB: 2048,
  1761  		DiskMB:   10000,
  1762  		IOPS:     100,
  1763  		Networks: []*NetworkResource{
  1764  			{
  1765  				CIDR:          "10.0.0.0/8",
  1766  				MBits:         100,
  1767  				ReservedPorts: []Port{{"ssh", 22}},
  1768  			},
  1769  		},
  1770  	}
  1771  	r2 := &Resources{
  1772  		CPU:      2000,
  1773  		MemoryMB: 1024,
  1774  		DiskMB:   5000,
  1775  		IOPS:     50,
  1776  		Networks: []*NetworkResource{
  1777  			{
  1778  				IP:            "10.0.0.1",
  1779  				MBits:         50,
  1780  				ReservedPorts: []Port{{"web", 80}},
  1781  			},
  1782  		},
  1783  	}
  1784  
  1785  	err := r1.Add(r2)
  1786  	if err != nil {
  1787  		t.Fatalf("Err: %v", err)
  1788  	}
  1789  
  1790  	expect := &Resources{
  1791  		CPU:      3000,
  1792  		MemoryMB: 3072,
  1793  		DiskMB:   15000,
  1794  		IOPS:     150,
  1795  		Networks: []*NetworkResource{
  1796  			{
  1797  				CIDR:          "10.0.0.0/8",
  1798  				MBits:         150,
  1799  				ReservedPorts: []Port{{"ssh", 22}, {"web", 80}},
  1800  			},
  1801  		},
  1802  	}
  1803  
  1804  	if !reflect.DeepEqual(expect.Networks, r1.Networks) {
  1805  		t.Fatalf("bad: %#v %#v", expect, r1)
  1806  	}
  1807  }
  1808  
  1809  func TestResource_Add_Network(t *testing.T) {
  1810  	r1 := &Resources{}
  1811  	r2 := &Resources{
  1812  		Networks: []*NetworkResource{
  1813  			{
  1814  				MBits:        50,
  1815  				DynamicPorts: []Port{{"http", 0}, {"https", 0}},
  1816  			},
  1817  		},
  1818  	}
  1819  	r3 := &Resources{
  1820  		Networks: []*NetworkResource{
  1821  			{
  1822  				MBits:        25,
  1823  				DynamicPorts: []Port{{"admin", 0}},
  1824  			},
  1825  		},
  1826  	}
  1827  
  1828  	err := r1.Add(r2)
  1829  	if err != nil {
  1830  		t.Fatalf("Err: %v", err)
  1831  	}
  1832  	err = r1.Add(r3)
  1833  	if err != nil {
  1834  		t.Fatalf("Err: %v", err)
  1835  	}
  1836  
  1837  	expect := &Resources{
  1838  		Networks: []*NetworkResource{
  1839  			{
  1840  				MBits:        75,
  1841  				DynamicPorts: []Port{{"http", 0}, {"https", 0}, {"admin", 0}},
  1842  			},
  1843  		},
  1844  	}
  1845  
  1846  	if !reflect.DeepEqual(expect.Networks, r1.Networks) {
  1847  		t.Fatalf("bad: %#v %#v", expect.Networks[0], r1.Networks[0])
  1848  	}
  1849  }
  1850  
  1851  func TestEncodeDecode(t *testing.T) {
  1852  	type FooRequest struct {
  1853  		Foo string
  1854  		Bar int
  1855  		Baz bool
  1856  	}
  1857  	arg := &FooRequest{
  1858  		Foo: "test",
  1859  		Bar: 42,
  1860  		Baz: true,
  1861  	}
  1862  	buf, err := Encode(1, arg)
  1863  	if err != nil {
  1864  		t.Fatalf("err: %v", err)
  1865  	}
  1866  
  1867  	var out FooRequest
  1868  	err = Decode(buf[1:], &out)
  1869  	if err != nil {
  1870  		t.Fatalf("err: %v", err)
  1871  	}
  1872  
  1873  	if !reflect.DeepEqual(arg, &out) {
  1874  		t.Fatalf("bad: %#v %#v", arg, out)
  1875  	}
  1876  }
  1877  
  1878  func BenchmarkEncodeDecode(b *testing.B) {
  1879  	job := testJob()
  1880  
  1881  	for i := 0; i < b.N; i++ {
  1882  		buf, err := Encode(1, job)
  1883  		if err != nil {
  1884  			b.Fatalf("err: %v", err)
  1885  		}
  1886  
  1887  		var out Job
  1888  		err = Decode(buf[1:], &out)
  1889  		if err != nil {
  1890  			b.Fatalf("err: %v", err)
  1891  		}
  1892  	}
  1893  }
  1894  
  1895  func TestInvalidServiceCheck(t *testing.T) {
  1896  	s := Service{
  1897  		Name:      "service-name",
  1898  		PortLabel: "bar",
  1899  		Checks: []*ServiceCheck{
  1900  			{
  1901  				Name: "check-name",
  1902  				Type: "lol",
  1903  			},
  1904  		},
  1905  	}
  1906  	if err := s.Validate(); err == nil {
  1907  		t.Fatalf("Service should be invalid (invalid type)")
  1908  	}
  1909  
  1910  	s = Service{
  1911  		Name:      "service.name",
  1912  		PortLabel: "bar",
  1913  	}
  1914  	if err := s.ValidateName(s.Name); err == nil {
  1915  		t.Fatalf("Service should be invalid (contains a dot): %v", err)
  1916  	}
  1917  
  1918  	s = Service{
  1919  		Name:      "-my-service",
  1920  		PortLabel: "bar",
  1921  	}
  1922  	if err := s.Validate(); err == nil {
  1923  		t.Fatalf("Service should be invalid (begins with a hyphen): %v", err)
  1924  	}
  1925  
  1926  	s = Service{
  1927  		Name:      "my-service-${NOMAD_META_FOO}",
  1928  		PortLabel: "bar",
  1929  	}
  1930  	if err := s.Validate(); err != nil {
  1931  		t.Fatalf("Service should be valid: %v", err)
  1932  	}
  1933  
  1934  	s = Service{
  1935  		Name:      "my_service-${NOMAD_META_FOO}",
  1936  		PortLabel: "bar",
  1937  	}
  1938  	if err := s.Validate(); err == nil {
  1939  		t.Fatalf("Service should be invalid (contains underscore but not in a variable name): %v", err)
  1940  	}
  1941  
  1942  	s = Service{
  1943  		Name:      "abcdef0123456789-abcdef0123456789-abcdef0123456789-abcdef0123456",
  1944  		PortLabel: "bar",
  1945  	}
  1946  	if err := s.ValidateName(s.Name); err == nil {
  1947  		t.Fatalf("Service should be invalid (too long): %v", err)
  1948  	}
  1949  
  1950  	s = Service{
  1951  		Name: "service-name",
  1952  		Checks: []*ServiceCheck{
  1953  			{
  1954  				Name:     "check-tcp",
  1955  				Type:     ServiceCheckTCP,
  1956  				Interval: 5 * time.Second,
  1957  				Timeout:  2 * time.Second,
  1958  			},
  1959  			{
  1960  				Name:     "check-http",
  1961  				Type:     ServiceCheckHTTP,
  1962  				Path:     "/foo",
  1963  				Interval: 5 * time.Second,
  1964  				Timeout:  2 * time.Second,
  1965  			},
  1966  		},
  1967  	}
  1968  	if err := s.Validate(); err == nil {
  1969  		t.Fatalf("service should be invalid (tcp/http checks with no port): %v", err)
  1970  	}
  1971  
  1972  	s = Service{
  1973  		Name: "service-name",
  1974  		Checks: []*ServiceCheck{
  1975  			{
  1976  				Name:     "check-script",
  1977  				Type:     ServiceCheckScript,
  1978  				Command:  "/bin/date",
  1979  				Interval: 5 * time.Second,
  1980  				Timeout:  2 * time.Second,
  1981  			},
  1982  		},
  1983  	}
  1984  	if err := s.Validate(); err != nil {
  1985  		t.Fatalf("un-expected error: %v", err)
  1986  	}
  1987  }
  1988  
  1989  func TestDistinctCheckID(t *testing.T) {
  1990  	c1 := ServiceCheck{
  1991  		Name:     "web-health",
  1992  		Type:     "http",
  1993  		Path:     "/health",
  1994  		Interval: 2 * time.Second,
  1995  		Timeout:  3 * time.Second,
  1996  	}
  1997  	c2 := ServiceCheck{
  1998  		Name:     "web-health",
  1999  		Type:     "http",
  2000  		Path:     "/health1",
  2001  		Interval: 2 * time.Second,
  2002  		Timeout:  3 * time.Second,
  2003  	}
  2004  
  2005  	c3 := ServiceCheck{
  2006  		Name:     "web-health",
  2007  		Type:     "http",
  2008  		Path:     "/health",
  2009  		Interval: 4 * time.Second,
  2010  		Timeout:  3 * time.Second,
  2011  	}
  2012  	serviceID := "123"
  2013  	c1Hash := c1.Hash(serviceID)
  2014  	c2Hash := c2.Hash(serviceID)
  2015  	c3Hash := c3.Hash(serviceID)
  2016  
  2017  	if c1Hash == c2Hash || c1Hash == c3Hash || c3Hash == c2Hash {
  2018  		t.Fatalf("Checks need to be uniq c1: %s, c2: %s, c3: %s", c1Hash, c2Hash, c3Hash)
  2019  	}
  2020  
  2021  }
  2022  
  2023  func TestService_Canonicalize(t *testing.T) {
  2024  	job := "example"
  2025  	taskGroup := "cache"
  2026  	task := "redis"
  2027  
  2028  	s := Service{
  2029  		Name: "${TASK}-db",
  2030  	}
  2031  
  2032  	s.Canonicalize(job, taskGroup, task)
  2033  	if s.Name != "redis-db" {
  2034  		t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name)
  2035  	}
  2036  
  2037  	s.Name = "db"
  2038  	s.Canonicalize(job, taskGroup, task)
  2039  	if s.Name != "db" {
  2040  		t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name)
  2041  	}
  2042  
  2043  	s.Name = "${JOB}-${TASKGROUP}-${TASK}-db"
  2044  	s.Canonicalize(job, taskGroup, task)
  2045  	if s.Name != "example-cache-redis-db" {
  2046  		t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name)
  2047  	}
  2048  
  2049  	s.Name = "${BASE}-db"
  2050  	s.Canonicalize(job, taskGroup, task)
  2051  	if s.Name != "example-cache-redis-db" {
  2052  		t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name)
  2053  	}
  2054  
  2055  }
  2056  
  2057  func TestJob_ExpandServiceNames(t *testing.T) {
  2058  	j := &Job{
  2059  		Name: "my-job",
  2060  		TaskGroups: []*TaskGroup{
  2061  			{
  2062  				Name: "web",
  2063  				Tasks: []*Task{
  2064  					{
  2065  						Name: "frontend",
  2066  						Services: []*Service{
  2067  							{
  2068  								Name: "${BASE}-default",
  2069  							},
  2070  							{
  2071  								Name: "jmx",
  2072  							},
  2073  						},
  2074  					},
  2075  				},
  2076  			},
  2077  			{
  2078  				Name: "admin",
  2079  				Tasks: []*Task{
  2080  					{
  2081  						Name: "admin-web",
  2082  					},
  2083  				},
  2084  			},
  2085  		},
  2086  	}
  2087  
  2088  	j.Canonicalize()
  2089  
  2090  	service1Name := j.TaskGroups[0].Tasks[0].Services[0].Name
  2091  	if service1Name != "my-job-web-frontend-default" {
  2092  		t.Fatalf("Expected Service Name: %s, Actual: %s", "my-job-web-frontend-default", service1Name)
  2093  	}
  2094  
  2095  	service2Name := j.TaskGroups[0].Tasks[0].Services[1].Name
  2096  	if service2Name != "jmx" {
  2097  		t.Fatalf("Expected Service Name: %s, Actual: %s", "jmx", service2Name)
  2098  	}
  2099  
  2100  }
  2101  
  2102  func TestPeriodicConfig_EnabledInvalid(t *testing.T) {
  2103  	// Create a config that is enabled but with no interval specified.
  2104  	p := &PeriodicConfig{Enabled: true}
  2105  	if err := p.Validate(); err == nil {
  2106  		t.Fatal("Enabled PeriodicConfig with no spec or type shouldn't be valid")
  2107  	}
  2108  
  2109  	// Create a config that is enabled, with a spec but no type specified.
  2110  	p = &PeriodicConfig{Enabled: true, Spec: "foo"}
  2111  	if err := p.Validate(); err == nil {
  2112  		t.Fatal("Enabled PeriodicConfig with no spec type shouldn't be valid")
  2113  	}
  2114  
  2115  	// Create a config that is enabled, with a spec type but no spec specified.
  2116  	p = &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron}
  2117  	if err := p.Validate(); err == nil {
  2118  		t.Fatal("Enabled PeriodicConfig with no spec shouldn't be valid")
  2119  	}
  2120  
  2121  	// Create a config that is enabled, with a bad time zone.
  2122  	p = &PeriodicConfig{Enabled: true, TimeZone: "FOO"}
  2123  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "time zone") {
  2124  		t.Fatalf("Enabled PeriodicConfig with bad time zone shouldn't be valid: %v", err)
  2125  	}
  2126  }
  2127  
  2128  func TestPeriodicConfig_InvalidCron(t *testing.T) {
  2129  	specs := []string{"foo", "* *", "@foo"}
  2130  	for _, spec := range specs {
  2131  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
  2132  		p.Canonicalize()
  2133  		if err := p.Validate(); err == nil {
  2134  			t.Fatal("Invalid cron spec")
  2135  		}
  2136  	}
  2137  }
  2138  
  2139  func TestPeriodicConfig_ValidCron(t *testing.T) {
  2140  	specs := []string{"0 0 29 2 *", "@hourly", "0 0-15 * * *"}
  2141  	for _, spec := range specs {
  2142  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
  2143  		p.Canonicalize()
  2144  		if err := p.Validate(); err != nil {
  2145  			t.Fatal("Passed valid cron")
  2146  		}
  2147  	}
  2148  }
  2149  
  2150  func TestPeriodicConfig_NextCron(t *testing.T) {
  2151  	from := time.Date(2009, time.November, 10, 23, 22, 30, 0, time.UTC)
  2152  	specs := []string{"0 0 29 2 * 1980", "*/5 * * * *"}
  2153  	expected := []time.Time{{}, time.Date(2009, time.November, 10, 23, 25, 0, 0, time.UTC)}
  2154  	for i, spec := range specs {
  2155  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
  2156  		p.Canonicalize()
  2157  		n := p.Next(from)
  2158  		if expected[i] != n {
  2159  			t.Fatalf("Next(%v) returned %v; want %v", from, n, expected[i])
  2160  		}
  2161  	}
  2162  }
  2163  
  2164  func TestPeriodicConfig_ValidTimeZone(t *testing.T) {
  2165  	zones := []string{"Africa/Abidjan", "America/Chicago", "Europe/Minsk", "UTC"}
  2166  	for _, zone := range zones {
  2167  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: "0 0 29 2 * 1980", TimeZone: zone}
  2168  		p.Canonicalize()
  2169  		if err := p.Validate(); err != nil {
  2170  			t.Fatalf("Valid tz errored: %v", err)
  2171  		}
  2172  	}
  2173  }
  2174  
  2175  func TestPeriodicConfig_DST(t *testing.T) {
  2176  	// On Sun, Mar 12, 2:00 am 2017: +1 hour UTC
  2177  	p := &PeriodicConfig{
  2178  		Enabled:  true,
  2179  		SpecType: PeriodicSpecCron,
  2180  		Spec:     "0 2 11-12 3 * 2017",
  2181  		TimeZone: "America/Los_Angeles",
  2182  	}
  2183  	p.Canonicalize()
  2184  
  2185  	t1 := time.Date(2017, time.March, 11, 1, 0, 0, 0, p.location)
  2186  	t2 := time.Date(2017, time.March, 12, 1, 0, 0, 0, p.location)
  2187  
  2188  	// E1 is an 8 hour adjustment, E2 is a 7 hour adjustment
  2189  	e1 := time.Date(2017, time.March, 11, 10, 0, 0, 0, time.UTC)
  2190  	e2 := time.Date(2017, time.March, 12, 9, 0, 0, 0, time.UTC)
  2191  
  2192  	n1 := p.Next(t1).UTC()
  2193  	n2 := p.Next(t2).UTC()
  2194  
  2195  	if !reflect.DeepEqual(e1, n1) {
  2196  		t.Fatalf("Got %v; want %v", n1, e1)
  2197  	}
  2198  	if !reflect.DeepEqual(e2, n2) {
  2199  		t.Fatalf("Got %v; want %v", n1, e1)
  2200  	}
  2201  }
  2202  
  2203  func TestRestartPolicy_Validate(t *testing.T) {
  2204  	// Policy with acceptable restart options passes
  2205  	p := &RestartPolicy{
  2206  		Mode:     RestartPolicyModeFail,
  2207  		Attempts: 0,
  2208  		Interval: 5 * time.Second,
  2209  	}
  2210  	if err := p.Validate(); err != nil {
  2211  		t.Fatalf("err: %v", err)
  2212  	}
  2213  
  2214  	// Policy with ambiguous restart options fails
  2215  	p = &RestartPolicy{
  2216  		Mode:     RestartPolicyModeDelay,
  2217  		Attempts: 0,
  2218  		Interval: 5 * time.Second,
  2219  	}
  2220  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "ambiguous") {
  2221  		t.Fatalf("expect ambiguity error, got: %v", err)
  2222  	}
  2223  
  2224  	// Bad policy mode fails
  2225  	p = &RestartPolicy{
  2226  		Mode:     "nope",
  2227  		Attempts: 1,
  2228  		Interval: 5 * time.Second,
  2229  	}
  2230  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "mode") {
  2231  		t.Fatalf("expect mode error, got: %v", err)
  2232  	}
  2233  
  2234  	// Fails when attempts*delay does not fit inside interval
  2235  	p = &RestartPolicy{
  2236  		Mode:     RestartPolicyModeDelay,
  2237  		Attempts: 3,
  2238  		Delay:    5 * time.Second,
  2239  		Interval: 5 * time.Second,
  2240  	}
  2241  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "can't restart") {
  2242  		t.Fatalf("expect restart interval error, got: %v", err)
  2243  	}
  2244  
  2245  	// Fails when interval is to small
  2246  	p = &RestartPolicy{
  2247  		Mode:     RestartPolicyModeDelay,
  2248  		Attempts: 3,
  2249  		Delay:    5 * time.Second,
  2250  		Interval: 2 * time.Second,
  2251  	}
  2252  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "Interval can not be less than") {
  2253  		t.Fatalf("expect interval too small error, got: %v", err)
  2254  	}
  2255  }
  2256  
  2257  func TestAllocation_Index(t *testing.T) {
  2258  	a1 := Allocation{
  2259  		Name:      "example.cache[1]",
  2260  		TaskGroup: "cache",
  2261  		JobID:     "example",
  2262  		Job: &Job{
  2263  			ID:         "example",
  2264  			TaskGroups: []*TaskGroup{{Name: "cache"}}},
  2265  	}
  2266  	e1 := uint(1)
  2267  	a2 := a1.Copy()
  2268  	a2.Name = "example.cache[713127]"
  2269  	e2 := uint(713127)
  2270  
  2271  	if a1.Index() != e1 || a2.Index() != e2 {
  2272  		t.Fatalf("Got %d and %d", a1.Index(), a2.Index())
  2273  	}
  2274  }
  2275  
  2276  func TestTaskArtifact_Validate_Source(t *testing.T) {
  2277  	valid := &TaskArtifact{GetterSource: "google.com"}
  2278  	if err := valid.Validate(); err != nil {
  2279  		t.Fatalf("unexpected error: %v", err)
  2280  	}
  2281  }
  2282  
  2283  func TestTaskArtifact_Validate_Dest(t *testing.T) {
  2284  	valid := &TaskArtifact{GetterSource: "google.com"}
  2285  	if err := valid.Validate(); err != nil {
  2286  		t.Fatalf("unexpected error: %v", err)
  2287  	}
  2288  
  2289  	valid.RelativeDest = "local/"
  2290  	if err := valid.Validate(); err != nil {
  2291  		t.Fatalf("unexpected error: %v", err)
  2292  	}
  2293  
  2294  	valid.RelativeDest = "local/.."
  2295  	if err := valid.Validate(); err != nil {
  2296  		t.Fatalf("unexpected error: %v", err)
  2297  	}
  2298  
  2299  	valid.RelativeDest = "local/../../.."
  2300  	if err := valid.Validate(); err == nil {
  2301  		t.Fatalf("expected error: %v", err)
  2302  	}
  2303  }
  2304  
  2305  func TestAllocation_ShouldMigrate(t *testing.T) {
  2306  	alloc := Allocation{
  2307  		PreviousAllocation: "123",
  2308  		TaskGroup:          "foo",
  2309  		Job: &Job{
  2310  			TaskGroups: []*TaskGroup{
  2311  				{
  2312  					Name: "foo",
  2313  					EphemeralDisk: &EphemeralDisk{
  2314  						Migrate: true,
  2315  						Sticky:  true,
  2316  					},
  2317  				},
  2318  			},
  2319  		},
  2320  	}
  2321  
  2322  	if !alloc.ShouldMigrate() {
  2323  		t.Fatalf("bad: %v", alloc)
  2324  	}
  2325  
  2326  	alloc1 := Allocation{
  2327  		PreviousAllocation: "123",
  2328  		TaskGroup:          "foo",
  2329  		Job: &Job{
  2330  			TaskGroups: []*TaskGroup{
  2331  				{
  2332  					Name:          "foo",
  2333  					EphemeralDisk: &EphemeralDisk{},
  2334  				},
  2335  			},
  2336  		},
  2337  	}
  2338  
  2339  	if alloc1.ShouldMigrate() {
  2340  		t.Fatalf("bad: %v", alloc)
  2341  	}
  2342  
  2343  	alloc2 := Allocation{
  2344  		PreviousAllocation: "123",
  2345  		TaskGroup:          "foo",
  2346  		Job: &Job{
  2347  			TaskGroups: []*TaskGroup{
  2348  				{
  2349  					Name: "foo",
  2350  					EphemeralDisk: &EphemeralDisk{
  2351  						Sticky:  false,
  2352  						Migrate: true,
  2353  					},
  2354  				},
  2355  			},
  2356  		},
  2357  	}
  2358  
  2359  	if alloc2.ShouldMigrate() {
  2360  		t.Fatalf("bad: %v", alloc)
  2361  	}
  2362  
  2363  	alloc3 := Allocation{
  2364  		PreviousAllocation: "123",
  2365  		TaskGroup:          "foo",
  2366  		Job: &Job{
  2367  			TaskGroups: []*TaskGroup{
  2368  				{
  2369  					Name: "foo",
  2370  				},
  2371  			},
  2372  		},
  2373  	}
  2374  
  2375  	if alloc3.ShouldMigrate() {
  2376  		t.Fatalf("bad: %v", alloc)
  2377  	}
  2378  
  2379  	// No previous
  2380  	alloc4 := Allocation{
  2381  		TaskGroup: "foo",
  2382  		Job: &Job{
  2383  			TaskGroups: []*TaskGroup{
  2384  				{
  2385  					Name: "foo",
  2386  					EphemeralDisk: &EphemeralDisk{
  2387  						Migrate: true,
  2388  						Sticky:  true,
  2389  					},
  2390  				},
  2391  			},
  2392  		},
  2393  	}
  2394  
  2395  	if alloc4.ShouldMigrate() {
  2396  		t.Fatalf("bad: %v", alloc4)
  2397  	}
  2398  }
  2399  
  2400  func TestTaskArtifact_Validate_Checksum(t *testing.T) {
  2401  	cases := []struct {
  2402  		Input *TaskArtifact
  2403  		Err   bool
  2404  	}{
  2405  		{
  2406  			&TaskArtifact{
  2407  				GetterSource: "foo.com",
  2408  				GetterOptions: map[string]string{
  2409  					"checksum": "no-type",
  2410  				},
  2411  			},
  2412  			true,
  2413  		},
  2414  		{
  2415  			&TaskArtifact{
  2416  				GetterSource: "foo.com",
  2417  				GetterOptions: map[string]string{
  2418  					"checksum": "md5:toosmall",
  2419  				},
  2420  			},
  2421  			true,
  2422  		},
  2423  		{
  2424  			&TaskArtifact{
  2425  				GetterSource: "foo.com",
  2426  				GetterOptions: map[string]string{
  2427  					"checksum": "invalid:type",
  2428  				},
  2429  			},
  2430  			true,
  2431  		},
  2432  	}
  2433  
  2434  	for i, tc := range cases {
  2435  		err := tc.Input.Validate()
  2436  		if (err != nil) != tc.Err {
  2437  			t.Fatalf("case %d: %v", i, err)
  2438  			continue
  2439  		}
  2440  	}
  2441  }
  2442  
  2443  func TestAllocation_Terminated(t *testing.T) {
  2444  	type desiredState struct {
  2445  		ClientStatus  string
  2446  		DesiredStatus string
  2447  		Terminated    bool
  2448  	}
  2449  
  2450  	harness := []desiredState{
  2451  		{
  2452  			ClientStatus:  AllocClientStatusPending,
  2453  			DesiredStatus: AllocDesiredStatusStop,
  2454  			Terminated:    false,
  2455  		},
  2456  		{
  2457  			ClientStatus:  AllocClientStatusRunning,
  2458  			DesiredStatus: AllocDesiredStatusStop,
  2459  			Terminated:    false,
  2460  		},
  2461  		{
  2462  			ClientStatus:  AllocClientStatusFailed,
  2463  			DesiredStatus: AllocDesiredStatusStop,
  2464  			Terminated:    true,
  2465  		},
  2466  		{
  2467  			ClientStatus:  AllocClientStatusFailed,
  2468  			DesiredStatus: AllocDesiredStatusRun,
  2469  			Terminated:    true,
  2470  		},
  2471  	}
  2472  
  2473  	for _, state := range harness {
  2474  		alloc := Allocation{}
  2475  		alloc.DesiredStatus = state.DesiredStatus
  2476  		alloc.ClientStatus = state.ClientStatus
  2477  		if alloc.Terminated() != state.Terminated {
  2478  			t.Fatalf("expected: %v, actual: %v", state.Terminated, alloc.Terminated())
  2479  		}
  2480  	}
  2481  }
  2482  
  2483  func TestVault_Validate(t *testing.T) {
  2484  	v := &Vault{
  2485  		Env:        true,
  2486  		ChangeMode: VaultChangeModeNoop,
  2487  	}
  2488  
  2489  	if err := v.Validate(); err == nil || !strings.Contains(err.Error(), "Policy list") {
  2490  		t.Fatalf("Expected policy list empty error")
  2491  	}
  2492  
  2493  	v.Policies = []string{"foo", "root"}
  2494  	v.ChangeMode = VaultChangeModeSignal
  2495  
  2496  	err := v.Validate()
  2497  	if err == nil {
  2498  		t.Fatalf("Expected validation errors")
  2499  	}
  2500  
  2501  	if !strings.Contains(err.Error(), "Signal must") {
  2502  		t.Fatalf("Expected signal empty error")
  2503  	}
  2504  	if !strings.Contains(err.Error(), "root") {
  2505  		t.Fatalf("Expected root error")
  2506  	}
  2507  }
  2508  
  2509  func TestParameterizedJobConfig_Validate(t *testing.T) {
  2510  	d := &ParameterizedJobConfig{
  2511  		Payload: "foo",
  2512  	}
  2513  
  2514  	if err := d.Validate(); err == nil || !strings.Contains(err.Error(), "payload") {
  2515  		t.Fatalf("Expected unknown payload requirement: %v", err)
  2516  	}
  2517  
  2518  	d.Payload = DispatchPayloadOptional
  2519  	d.MetaOptional = []string{"foo", "bar"}
  2520  	d.MetaRequired = []string{"bar", "baz"}
  2521  
  2522  	if err := d.Validate(); err == nil || !strings.Contains(err.Error(), "disjoint") {
  2523  		t.Fatalf("Expected meta not being disjoint error: %v", err)
  2524  	}
  2525  }
  2526  
  2527  func TestParameterizedJobConfig_Validate_NonBatch(t *testing.T) {
  2528  	job := testJob()
  2529  	job.ParameterizedJob = &ParameterizedJobConfig{
  2530  		Payload: DispatchPayloadOptional,
  2531  	}
  2532  	job.Type = JobTypeSystem
  2533  
  2534  	if err := job.Validate(); err == nil || !strings.Contains(err.Error(), "only be used with") {
  2535  		t.Fatalf("Expected bad scheduler tpye: %v", err)
  2536  	}
  2537  }
  2538  
  2539  func TestParameterizedJobConfig_Canonicalize(t *testing.T) {
  2540  	d := &ParameterizedJobConfig{}
  2541  	d.Canonicalize()
  2542  	if d.Payload != DispatchPayloadOptional {
  2543  		t.Fatalf("Canonicalize failed")
  2544  	}
  2545  }
  2546  
  2547  func TestDispatchPayloadConfig_Validate(t *testing.T) {
  2548  	d := &DispatchPayloadConfig{
  2549  		File: "foo",
  2550  	}
  2551  
  2552  	// task/local/haha
  2553  	if err := d.Validate(); err != nil {
  2554  		t.Fatalf("bad: %v", err)
  2555  	}
  2556  
  2557  	// task/haha
  2558  	d.File = "../haha"
  2559  	if err := d.Validate(); err != nil {
  2560  		t.Fatalf("bad: %v", err)
  2561  	}
  2562  
  2563  	// ../haha
  2564  	d.File = "../../../haha"
  2565  	if err := d.Validate(); err == nil {
  2566  		t.Fatalf("bad: %v", err)
  2567  	}
  2568  }
  2569  
  2570  func TestIsRecoverable(t *testing.T) {
  2571  	if IsRecoverable(nil) {
  2572  		t.Errorf("nil should not be recoverable")
  2573  	}
  2574  	if IsRecoverable(NewRecoverableError(nil, true)) {
  2575  		t.Errorf("NewRecoverableError(nil, true) should not be recoverable")
  2576  	}
  2577  	if IsRecoverable(fmt.Errorf("i promise im recoverable")) {
  2578  		t.Errorf("Custom errors should not be recoverable")
  2579  	}
  2580  	if IsRecoverable(NewRecoverableError(fmt.Errorf(""), false)) {
  2581  		t.Errorf("Explicitly unrecoverable errors should not be recoverable")
  2582  	}
  2583  	if !IsRecoverable(NewRecoverableError(fmt.Errorf(""), true)) {
  2584  		t.Errorf("Explicitly recoverable errors *should* be recoverable")
  2585  	}
  2586  }
  2587  
  2588  func TestACLTokenValidate(t *testing.T) {
  2589  	tk := &ACLToken{}
  2590  
  2591  	// Mising a type
  2592  	err := tk.Validate()
  2593  	assert.NotNil(t, err)
  2594  	if !strings.Contains(err.Error(), "client or management") {
  2595  		t.Fatalf("bad: %v", err)
  2596  	}
  2597  
  2598  	// Missing policies
  2599  	tk.Type = ACLClientToken
  2600  	err = tk.Validate()
  2601  	assert.NotNil(t, err)
  2602  	if !strings.Contains(err.Error(), "missing policies") {
  2603  		t.Fatalf("bad: %v", err)
  2604  	}
  2605  
  2606  	// Invalid policices
  2607  	tk.Type = ACLManagementToken
  2608  	tk.Policies = []string{"foo"}
  2609  	err = tk.Validate()
  2610  	assert.NotNil(t, err)
  2611  	if !strings.Contains(err.Error(), "associated with policies") {
  2612  		t.Fatalf("bad: %v", err)
  2613  	}
  2614  
  2615  	// Name too long policices
  2616  	tk.Name = uuid.Generate() + uuid.Generate()
  2617  	tk.Policies = nil
  2618  	err = tk.Validate()
  2619  	assert.NotNil(t, err)
  2620  	if !strings.Contains(err.Error(), "too long") {
  2621  		t.Fatalf("bad: %v", err)
  2622  	}
  2623  
  2624  	// Make it valid
  2625  	tk.Name = "foo"
  2626  	err = tk.Validate()
  2627  	assert.Nil(t, err)
  2628  }
  2629  
  2630  func TestACLTokenPolicySubset(t *testing.T) {
  2631  	tk := &ACLToken{
  2632  		Type:     ACLClientToken,
  2633  		Policies: []string{"foo", "bar", "baz"},
  2634  	}
  2635  
  2636  	assert.Equal(t, true, tk.PolicySubset([]string{"foo", "bar", "baz"}))
  2637  	assert.Equal(t, true, tk.PolicySubset([]string{"foo", "bar"}))
  2638  	assert.Equal(t, true, tk.PolicySubset([]string{"foo"}))
  2639  	assert.Equal(t, true, tk.PolicySubset([]string{}))
  2640  	assert.Equal(t, false, tk.PolicySubset([]string{"foo", "bar", "new"}))
  2641  	assert.Equal(t, false, tk.PolicySubset([]string{"new"}))
  2642  
  2643  	tk = &ACLToken{
  2644  		Type: ACLManagementToken,
  2645  	}
  2646  
  2647  	assert.Equal(t, true, tk.PolicySubset([]string{"foo", "bar", "baz"}))
  2648  	assert.Equal(t, true, tk.PolicySubset([]string{"foo", "bar"}))
  2649  	assert.Equal(t, true, tk.PolicySubset([]string{"foo"}))
  2650  	assert.Equal(t, true, tk.PolicySubset([]string{}))
  2651  	assert.Equal(t, true, tk.PolicySubset([]string{"foo", "bar", "new"}))
  2652  	assert.Equal(t, true, tk.PolicySubset([]string{"new"}))
  2653  }
  2654  
  2655  func TestACLTokenSetHash(t *testing.T) {
  2656  	tk := &ACLToken{
  2657  		Name:     "foo",
  2658  		Type:     ACLClientToken,
  2659  		Policies: []string{"foo", "bar"},
  2660  		Global:   false,
  2661  	}
  2662  	out1 := tk.SetHash()
  2663  	assert.NotNil(t, out1)
  2664  	assert.NotNil(t, tk.Hash)
  2665  	assert.Equal(t, out1, tk.Hash)
  2666  
  2667  	tk.Policies = []string{"foo"}
  2668  	out2 := tk.SetHash()
  2669  	assert.NotNil(t, out2)
  2670  	assert.NotNil(t, tk.Hash)
  2671  	assert.Equal(t, out2, tk.Hash)
  2672  	assert.NotEqual(t, out1, out2)
  2673  }
  2674  
  2675  func TestACLPolicySetHash(t *testing.T) {
  2676  	ap := &ACLPolicy{
  2677  		Name:        "foo",
  2678  		Description: "great policy",
  2679  		Rules:       "node { policy = \"read\" }",
  2680  	}
  2681  	out1 := ap.SetHash()
  2682  	assert.NotNil(t, out1)
  2683  	assert.NotNil(t, ap.Hash)
  2684  	assert.Equal(t, out1, ap.Hash)
  2685  
  2686  	ap.Rules = "node { policy = \"write\" }"
  2687  	out2 := ap.SetHash()
  2688  	assert.NotNil(t, out2)
  2689  	assert.NotNil(t, ap.Hash)
  2690  	assert.Equal(t, out2, ap.Hash)
  2691  	assert.NotEqual(t, out1, out2)
  2692  }
  2693  
  2694  func TestTaskEventPopulate(t *testing.T) {
  2695  	prepopulatedEvent := NewTaskEvent(TaskSetup)
  2696  	prepopulatedEvent.DisplayMessage = "Hola"
  2697  	testcases := []struct {
  2698  		event       *TaskEvent
  2699  		expectedMsg string
  2700  	}{
  2701  		{nil, ""},
  2702  		{prepopulatedEvent, "Hola"},
  2703  		{NewTaskEvent(TaskSetup).SetMessage("Setup"), "Setup"},
  2704  		{NewTaskEvent(TaskStarted), "Task started by client"},
  2705  		{NewTaskEvent(TaskReceived), "Task received by client"},
  2706  		{NewTaskEvent(TaskFailedValidation), "Validation of task failed"},
  2707  		{NewTaskEvent(TaskFailedValidation).SetValidationError(fmt.Errorf("task failed validation")), "task failed validation"},
  2708  		{NewTaskEvent(TaskSetupFailure), "Task setup failed"},
  2709  		{NewTaskEvent(TaskSetupFailure).SetSetupError(fmt.Errorf("task failed setup")), "task failed setup"},
  2710  		{NewTaskEvent(TaskDriverFailure), "Failed to start task"},
  2711  		{NewTaskEvent(TaskDownloadingArtifacts), "Client is downloading artifacts"},
  2712  		{NewTaskEvent(TaskArtifactDownloadFailed), "Failed to download artifacts"},
  2713  		{NewTaskEvent(TaskArtifactDownloadFailed).SetDownloadError(fmt.Errorf("connection reset by peer")), "connection reset by peer"},
  2714  		{NewTaskEvent(TaskRestarting).SetRestartDelay(2 * time.Second).SetRestartReason(ReasonWithinPolicy), "Task restarting in 2s"},
  2715  		{NewTaskEvent(TaskRestarting).SetRestartReason("Chaos Monkey did it"), "Chaos Monkey did it - Task restarting in 0s"},
  2716  		{NewTaskEvent(TaskKilling), "Sent interrupt"},
  2717  		{NewTaskEvent(TaskKilling).SetKillReason("Its time for you to die"), "Killing task: Its time for you to die"},
  2718  		{NewTaskEvent(TaskKilling).SetKillTimeout(1 * time.Second), "Sent interrupt. Waiting 1s before force killing"},
  2719  		{NewTaskEvent(TaskTerminated).SetExitCode(-1).SetSignal(3), "Exit Code: -1, Signal: 3"},
  2720  		{NewTaskEvent(TaskTerminated).SetMessage("Goodbye"), "Exit Code: 0, Exit Message: \"Goodbye\""},
  2721  		{NewTaskEvent(TaskKilled), "Task successfully killed"},
  2722  		{NewTaskEvent(TaskKilled).SetKillError(fmt.Errorf("undead creatures can't be killed")), "undead creatures can't be killed"},
  2723  		{NewTaskEvent(TaskNotRestarting).SetRestartReason("Chaos Monkey did it"), "Chaos Monkey did it"},
  2724  		{NewTaskEvent(TaskNotRestarting), "Task exceeded restart policy"},
  2725  		{NewTaskEvent(TaskLeaderDead), "Leader Task in Group dead"},
  2726  		{NewTaskEvent(TaskSiblingFailed), "Task's sibling failed"},
  2727  		{NewTaskEvent(TaskSiblingFailed).SetFailedSibling("patient zero"), "Task's sibling \"patient zero\" failed"},
  2728  		{NewTaskEvent(TaskSignaling), "Task being sent a signal"},
  2729  		{NewTaskEvent(TaskSignaling).SetTaskSignal(os.Interrupt), "Task being sent signal interrupt"},
  2730  		{NewTaskEvent(TaskSignaling).SetTaskSignal(os.Interrupt).SetTaskSignalReason("process interrupted"), "Task being sent signal interrupt: process interrupted"},
  2731  		{NewTaskEvent(TaskRestartSignal), "Task signaled to restart"},
  2732  		{NewTaskEvent(TaskRestartSignal).SetRestartReason("Chaos Monkey restarted it"), "Chaos Monkey restarted it"},
  2733  		{NewTaskEvent(TaskDriverMessage).SetDriverMessage("YOLO"), "YOLO"},
  2734  		{NewTaskEvent("Unknown Type, No message"), ""},
  2735  		{NewTaskEvent("Unknown Type").SetMessage("Hello world"), "Hello world"},
  2736  	}
  2737  
  2738  	for _, tc := range testcases {
  2739  		tc.event.PopulateEventDisplayMessage()
  2740  		if tc.event != nil && tc.event.DisplayMessage != tc.expectedMsg {
  2741  			t.Fatalf("Expected %v but got %v", tc.expectedMsg, tc.event.DisplayMessage)
  2742  		}
  2743  	}
  2744  }