github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/nomad/structs/structs_test.go (about)

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