github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/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  )
    13  
    14  func TestJob_Validate(t *testing.T) {
    15  	j := &Job{}
    16  	err := j.Validate()
    17  	mErr := err.(*multierror.Error)
    18  	if !strings.Contains(mErr.Errors[0].Error(), "job region") {
    19  		t.Fatalf("err: %s", err)
    20  	}
    21  	if !strings.Contains(mErr.Errors[1].Error(), "job ID") {
    22  		t.Fatalf("err: %s", err)
    23  	}
    24  	if !strings.Contains(mErr.Errors[2].Error(), "job name") {
    25  		t.Fatalf("err: %s", err)
    26  	}
    27  	if !strings.Contains(mErr.Errors[3].Error(), "job type") {
    28  		t.Fatalf("err: %s", err)
    29  	}
    30  	if !strings.Contains(mErr.Errors[4].Error(), "priority") {
    31  		t.Fatalf("err: %s", err)
    32  	}
    33  	if !strings.Contains(mErr.Errors[5].Error(), "datacenters") {
    34  		t.Fatalf("err: %s", err)
    35  	}
    36  	if !strings.Contains(mErr.Errors[6].Error(), "task groups") {
    37  		t.Fatalf("err: %s", err)
    38  	}
    39  
    40  	j = &Job{
    41  		Type: JobTypeService,
    42  		Periodic: &PeriodicConfig{
    43  			Enabled: true,
    44  		},
    45  	}
    46  	err = j.Validate()
    47  	mErr = err.(*multierror.Error)
    48  	if !strings.Contains(mErr.Error(), "Periodic") {
    49  		t.Fatalf("err: %s", err)
    50  	}
    51  
    52  	j = &Job{
    53  		Region:      "global",
    54  		ID:          GenerateUUID(),
    55  		Name:        "my-job",
    56  		Type:        JobTypeService,
    57  		Priority:    50,
    58  		Datacenters: []string{"dc1"},
    59  		TaskGroups: []*TaskGroup{
    60  			&TaskGroup{
    61  				Name: "web",
    62  				RestartPolicy: &RestartPolicy{
    63  					Interval: 5 * time.Minute,
    64  					Delay:    10 * time.Second,
    65  					Attempts: 10,
    66  				},
    67  			},
    68  			&TaskGroup{
    69  				Name: "web",
    70  				RestartPolicy: &RestartPolicy{
    71  					Interval: 5 * time.Minute,
    72  					Delay:    10 * time.Second,
    73  					Attempts: 10,
    74  				},
    75  			},
    76  			&TaskGroup{
    77  				RestartPolicy: &RestartPolicy{
    78  					Interval: 5 * time.Minute,
    79  					Delay:    10 * time.Second,
    80  					Attempts: 10,
    81  				},
    82  			},
    83  		},
    84  	}
    85  	err = j.Validate()
    86  	mErr = err.(*multierror.Error)
    87  	if !strings.Contains(mErr.Errors[0].Error(), "2 redefines 'web' from group 1") {
    88  		t.Fatalf("err: %s", err)
    89  	}
    90  	if !strings.Contains(mErr.Errors[1].Error(), "group 3 missing name") {
    91  		t.Fatalf("err: %s", err)
    92  	}
    93  	if !strings.Contains(mErr.Errors[2].Error(), "Task group web validation failed") {
    94  		t.Fatalf("err: %s", err)
    95  	}
    96  }
    97  
    98  func testJob() *Job {
    99  	return &Job{
   100  		Region:      "global",
   101  		ID:          GenerateUUID(),
   102  		Name:        "my-job",
   103  		Type:        JobTypeService,
   104  		Priority:    50,
   105  		AllAtOnce:   false,
   106  		Datacenters: []string{"dc1"},
   107  		Constraints: []*Constraint{
   108  			&Constraint{
   109  				LTarget: "$attr.kernel.name",
   110  				RTarget: "linux",
   111  				Operand: "=",
   112  			},
   113  		},
   114  		Periodic: &PeriodicConfig{
   115  			Enabled: false,
   116  		},
   117  		TaskGroups: []*TaskGroup{
   118  			&TaskGroup{
   119  				Name:          "web",
   120  				Count:         10,
   121  				EphemeralDisk: DefaultEphemeralDisk(),
   122  				RestartPolicy: &RestartPolicy{
   123  					Mode:     RestartPolicyModeFail,
   124  					Attempts: 3,
   125  					Interval: 10 * time.Minute,
   126  					Delay:    1 * time.Minute,
   127  				},
   128  				Tasks: []*Task{
   129  					&Task{
   130  						Name:   "web",
   131  						Driver: "exec",
   132  						Config: map[string]interface{}{
   133  							"command": "/bin/date",
   134  						},
   135  						Env: map[string]string{
   136  							"FOO": "bar",
   137  						},
   138  						Artifacts: []*TaskArtifact{
   139  							{
   140  								GetterSource: "http://foo.com",
   141  							},
   142  						},
   143  						Services: []*Service{
   144  							{
   145  								Name:      "${TASK}-frontend",
   146  								PortLabel: "http",
   147  							},
   148  						},
   149  						Resources: &Resources{
   150  							CPU:      500,
   151  							MemoryMB: 256,
   152  							Networks: []*NetworkResource{
   153  								&NetworkResource{
   154  									MBits:        50,
   155  									DynamicPorts: []Port{{Label: "http"}},
   156  								},
   157  							},
   158  						},
   159  						LogConfig: &LogConfig{
   160  							MaxFiles:      10,
   161  							MaxFileSizeMB: 1,
   162  						},
   163  					},
   164  				},
   165  				Meta: map[string]string{
   166  					"elb_check_type":     "http",
   167  					"elb_check_interval": "30s",
   168  					"elb_check_min":      "3",
   169  				},
   170  			},
   171  		},
   172  		Meta: map[string]string{
   173  			"owner": "armon",
   174  		},
   175  	}
   176  }
   177  
   178  func TestJob_Copy(t *testing.T) {
   179  	j := testJob()
   180  	c := j.Copy()
   181  	if !reflect.DeepEqual(j, c) {
   182  		t.Fatalf("Copy() returned an unequal Job; got %#v; want %#v", c, j)
   183  	}
   184  }
   185  
   186  func TestJob_IsPeriodic(t *testing.T) {
   187  	j := &Job{
   188  		Type: JobTypeService,
   189  		Periodic: &PeriodicConfig{
   190  			Enabled: true,
   191  		},
   192  	}
   193  	if !j.IsPeriodic() {
   194  		t.Fatalf("IsPeriodic() returned false on periodic job")
   195  	}
   196  
   197  	j = &Job{
   198  		Type: JobTypeService,
   199  	}
   200  	if j.IsPeriodic() {
   201  		t.Fatalf("IsPeriodic() returned true on non-periodic job")
   202  	}
   203  }
   204  
   205  func TestJob_SystemJob_Validate(t *testing.T) {
   206  	j := testJob()
   207  	j.Type = JobTypeSystem
   208  	j.Canonicalize()
   209  
   210  	err := j.Validate()
   211  	if err == nil || !strings.Contains(err.Error(), "exceed") {
   212  		t.Fatalf("expect error due to count")
   213  	}
   214  
   215  	j.TaskGroups[0].Count = 0
   216  	if err := j.Validate(); err != nil {
   217  		t.Fatalf("unexpected err: %v", err)
   218  	}
   219  
   220  	j.TaskGroups[0].Count = 1
   221  	if err := j.Validate(); err != nil {
   222  		t.Fatalf("unexpected err: %v", err)
   223  	}
   224  }
   225  
   226  func TestJob_VaultPolicies(t *testing.T) {
   227  	j0 := &Job{}
   228  	e0 := make(map[string]map[string]*Vault, 0)
   229  
   230  	vj1 := &Vault{
   231  		Policies: []string{
   232  			"p1",
   233  			"p2",
   234  		},
   235  	}
   236  	vj2 := &Vault{
   237  		Policies: []string{
   238  			"p3",
   239  			"p4",
   240  		},
   241  	}
   242  	vj3 := &Vault{
   243  		Policies: []string{
   244  			"p5",
   245  		},
   246  	}
   247  	j1 := &Job{
   248  		TaskGroups: []*TaskGroup{
   249  			&TaskGroup{
   250  				Name: "foo",
   251  				Tasks: []*Task{
   252  					&Task{
   253  						Name: "t1",
   254  					},
   255  					&Task{
   256  						Name:  "t2",
   257  						Vault: vj1,
   258  					},
   259  				},
   260  			},
   261  			&TaskGroup{
   262  				Name: "bar",
   263  				Tasks: []*Task{
   264  					&Task{
   265  						Name:  "t3",
   266  						Vault: vj2,
   267  					},
   268  					&Task{
   269  						Name:  "t4",
   270  						Vault: vj3,
   271  					},
   272  				},
   273  			},
   274  		},
   275  	}
   276  
   277  	e1 := map[string]map[string]*Vault{
   278  		"foo": map[string]*Vault{
   279  			"t2": vj1,
   280  		},
   281  		"bar": map[string]*Vault{
   282  			"t3": vj2,
   283  			"t4": vj3,
   284  		},
   285  	}
   286  
   287  	cases := []struct {
   288  		Job      *Job
   289  		Expected map[string]map[string]*Vault
   290  	}{
   291  		{
   292  			Job:      j0,
   293  			Expected: e0,
   294  		},
   295  		{
   296  			Job:      j1,
   297  			Expected: e1,
   298  		},
   299  	}
   300  
   301  	for i, c := range cases {
   302  		got := c.Job.VaultPolicies()
   303  		if !reflect.DeepEqual(got, c.Expected) {
   304  			t.Fatalf("case %d: got %#v; want %#v", i+1, got, c.Expected)
   305  		}
   306  	}
   307  }
   308  
   309  func TestJob_RequiredSignals(t *testing.T) {
   310  	j0 := &Job{}
   311  	e0 := make(map[string]map[string][]string, 0)
   312  
   313  	vj1 := &Vault{
   314  		Policies:   []string{"p1"},
   315  		ChangeMode: VaultChangeModeNoop,
   316  	}
   317  	vj2 := &Vault{
   318  		Policies:     []string{"p1"},
   319  		ChangeMode:   VaultChangeModeSignal,
   320  		ChangeSignal: "SIGUSR1",
   321  	}
   322  	tj1 := &Template{
   323  		SourcePath: "foo",
   324  		DestPath:   "bar",
   325  		ChangeMode: TemplateChangeModeNoop,
   326  	}
   327  	tj2 := &Template{
   328  		SourcePath:   "foo",
   329  		DestPath:     "bar",
   330  		ChangeMode:   TemplateChangeModeSignal,
   331  		ChangeSignal: "SIGUSR2",
   332  	}
   333  	j1 := &Job{
   334  		TaskGroups: []*TaskGroup{
   335  			&TaskGroup{
   336  				Name: "foo",
   337  				Tasks: []*Task{
   338  					&Task{
   339  						Name: "t1",
   340  					},
   341  					&Task{
   342  						Name:      "t2",
   343  						Vault:     vj2,
   344  						Templates: []*Template{tj2},
   345  					},
   346  				},
   347  			},
   348  			&TaskGroup{
   349  				Name: "bar",
   350  				Tasks: []*Task{
   351  					&Task{
   352  						Name:      "t3",
   353  						Vault:     vj1,
   354  						Templates: []*Template{tj1},
   355  					},
   356  					&Task{
   357  						Name:  "t4",
   358  						Vault: vj2,
   359  					},
   360  				},
   361  			},
   362  		},
   363  	}
   364  
   365  	e1 := map[string]map[string][]string{
   366  		"foo": map[string][]string{
   367  			"t2": []string{"SIGUSR1", "SIGUSR2"},
   368  		},
   369  		"bar": map[string][]string{
   370  			"t4": []string{"SIGUSR1"},
   371  		},
   372  	}
   373  
   374  	cases := []struct {
   375  		Job      *Job
   376  		Expected map[string]map[string][]string
   377  	}{
   378  		{
   379  			Job:      j0,
   380  			Expected: e0,
   381  		},
   382  		{
   383  			Job:      j1,
   384  			Expected: e1,
   385  		},
   386  	}
   387  
   388  	for i, c := range cases {
   389  		got := c.Job.RequiredSignals()
   390  		if !reflect.DeepEqual(got, c.Expected) {
   391  			t.Fatalf("case %d: got %#v; want %#v", i+1, got, c.Expected)
   392  		}
   393  	}
   394  }
   395  
   396  func TestTaskGroup_Validate(t *testing.T) {
   397  	tg := &TaskGroup{
   398  		Count: -1,
   399  		RestartPolicy: &RestartPolicy{
   400  			Interval: 5 * time.Minute,
   401  			Delay:    10 * time.Second,
   402  			Attempts: 10,
   403  			Mode:     RestartPolicyModeDelay,
   404  		},
   405  	}
   406  	err := tg.Validate()
   407  	mErr := err.(*multierror.Error)
   408  	if !strings.Contains(mErr.Errors[0].Error(), "group name") {
   409  		t.Fatalf("err: %s", err)
   410  	}
   411  	if !strings.Contains(mErr.Errors[1].Error(), "count can't be negative") {
   412  		t.Fatalf("err: %s", err)
   413  	}
   414  	if !strings.Contains(mErr.Errors[2].Error(), "Missing tasks") {
   415  		t.Fatalf("err: %s", err)
   416  	}
   417  
   418  	tg = &TaskGroup{
   419  		Name:  "web",
   420  		Count: 1,
   421  		Tasks: []*Task{
   422  			&Task{Name: "web", Leader: true},
   423  			&Task{Name: "web", Leader: true},
   424  			&Task{},
   425  		},
   426  		RestartPolicy: &RestartPolicy{
   427  			Interval: 5 * time.Minute,
   428  			Delay:    10 * time.Second,
   429  			Attempts: 10,
   430  			Mode:     RestartPolicyModeDelay,
   431  		},
   432  	}
   433  
   434  	err = tg.Validate()
   435  	mErr = err.(*multierror.Error)
   436  	if !strings.Contains(mErr.Errors[0].Error(), "should have an ephemeral disk object") {
   437  		t.Fatalf("err: %s", err)
   438  	}
   439  	if !strings.Contains(mErr.Errors[1].Error(), "2 redefines 'web' from task 1") {
   440  		t.Fatalf("err: %s", err)
   441  	}
   442  	if !strings.Contains(mErr.Errors[2].Error(), "Task 3 missing name") {
   443  		t.Fatalf("err: %s", err)
   444  	}
   445  	if !strings.Contains(mErr.Errors[3].Error(), "Only one task may be marked as leader") {
   446  		t.Fatalf("err: %s", err)
   447  	}
   448  	if !strings.Contains(mErr.Errors[4].Error(), "Task web validation failed") {
   449  		t.Fatalf("err: %s", err)
   450  	}
   451  }
   452  
   453  func TestTask_Validate(t *testing.T) {
   454  	task := &Task{}
   455  	ephemeralDisk := DefaultEphemeralDisk()
   456  	err := task.Validate(ephemeralDisk)
   457  	mErr := err.(*multierror.Error)
   458  	if !strings.Contains(mErr.Errors[0].Error(), "task name") {
   459  		t.Fatalf("err: %s", err)
   460  	}
   461  	if !strings.Contains(mErr.Errors[1].Error(), "task driver") {
   462  		t.Fatalf("err: %s", err)
   463  	}
   464  	if !strings.Contains(mErr.Errors[2].Error(), "task resources") {
   465  		t.Fatalf("err: %s", err)
   466  	}
   467  
   468  	task = &Task{Name: "web/foo"}
   469  	err = task.Validate(ephemeralDisk)
   470  	mErr = err.(*multierror.Error)
   471  	if !strings.Contains(mErr.Errors[0].Error(), "slashes") {
   472  		t.Fatalf("err: %s", err)
   473  	}
   474  
   475  	task = &Task{
   476  		Name:   "web",
   477  		Driver: "docker",
   478  		Resources: &Resources{
   479  			CPU:      100,
   480  			MemoryMB: 100,
   481  			IOPS:     10,
   482  		},
   483  		LogConfig: DefaultLogConfig(),
   484  	}
   485  	ephemeralDisk.SizeMB = 200
   486  	err = task.Validate(ephemeralDisk)
   487  	if err != nil {
   488  		t.Fatalf("err: %s", err)
   489  	}
   490  
   491  	task.Constraints = append(task.Constraints,
   492  		&Constraint{
   493  			Operand: ConstraintDistinctHosts,
   494  		},
   495  		&Constraint{
   496  			Operand: ConstraintDistinctProperty,
   497  			LTarget: "${meta.rack}",
   498  		})
   499  
   500  	err = task.Validate(ephemeralDisk)
   501  	mErr = err.(*multierror.Error)
   502  	if !strings.Contains(mErr.Errors[0].Error(), "task level: distinct_hosts") {
   503  		t.Fatalf("err: %s", err)
   504  	}
   505  	if !strings.Contains(mErr.Errors[1].Error(), "task level: distinct_property") {
   506  		t.Fatalf("err: %s", err)
   507  	}
   508  }
   509  
   510  func TestTask_Validate_Services(t *testing.T) {
   511  	s1 := &Service{
   512  		Name:      "service-name",
   513  		PortLabel: "bar",
   514  		Checks: []*ServiceCheck{
   515  			{
   516  				Name:     "check-name",
   517  				Type:     ServiceCheckTCP,
   518  				Interval: 0 * time.Second,
   519  			},
   520  			{
   521  				Name:    "check-name",
   522  				Type:    ServiceCheckTCP,
   523  				Timeout: 2 * time.Second,
   524  			},
   525  			{
   526  				Name:     "check-name",
   527  				Type:     ServiceCheckTCP,
   528  				Interval: 1 * time.Second,
   529  			},
   530  		},
   531  	}
   532  
   533  	s2 := &Service{
   534  		Name:      "service-name",
   535  		PortLabel: "bar",
   536  	}
   537  
   538  	s3 := &Service{
   539  		Name:      "service-A",
   540  		PortLabel: "a",
   541  	}
   542  	s4 := &Service{
   543  		Name:      "service-A",
   544  		PortLabel: "b",
   545  	}
   546  
   547  	ephemeralDisk := DefaultEphemeralDisk()
   548  	ephemeralDisk.SizeMB = 200
   549  	task := &Task{
   550  		Name:   "web",
   551  		Driver: "docker",
   552  		Resources: &Resources{
   553  			CPU:      100,
   554  			MemoryMB: 100,
   555  			IOPS:     10,
   556  		},
   557  		Services: []*Service{s1, s2},
   558  	}
   559  
   560  	task1 := &Task{
   561  		Name:      "web",
   562  		Driver:    "docker",
   563  		Resources: DefaultResources(),
   564  		Services:  []*Service{s3, s4},
   565  		LogConfig: DefaultLogConfig(),
   566  	}
   567  	task1.Resources.Networks = []*NetworkResource{
   568  		&NetworkResource{
   569  			MBits: 10,
   570  			DynamicPorts: []Port{
   571  				Port{
   572  					Label: "a",
   573  					Value: 1000,
   574  				},
   575  				Port{
   576  					Label: "b",
   577  					Value: 2000,
   578  				},
   579  			},
   580  		},
   581  	}
   582  
   583  	err := task.Validate(ephemeralDisk)
   584  	if err == nil {
   585  		t.Fatal("expected an error")
   586  	}
   587  
   588  	if !strings.Contains(err.Error(), "service \"service-name\" is duplicate") {
   589  		t.Fatalf("err: %v", err)
   590  	}
   591  
   592  	if !strings.Contains(err.Error(), "check \"check-name\" is duplicate") {
   593  		t.Fatalf("err: %v", err)
   594  	}
   595  
   596  	if !strings.Contains(err.Error(), "missing required value interval") {
   597  		t.Fatalf("err: %v", err)
   598  	}
   599  
   600  	if !strings.Contains(err.Error(), "cannot be less than") {
   601  		t.Fatalf("err: %v", err)
   602  	}
   603  
   604  	if err = task1.Validate(ephemeralDisk); err != nil {
   605  		t.Fatalf("err : %v", err)
   606  	}
   607  }
   608  
   609  func TestTask_Validate_Service_Check(t *testing.T) {
   610  
   611  	check1 := ServiceCheck{
   612  		Name:     "check-name",
   613  		Type:     ServiceCheckTCP,
   614  		Interval: 10 * time.Second,
   615  		Timeout:  2 * time.Second,
   616  	}
   617  
   618  	err := check1.validate()
   619  	if err != nil {
   620  		t.Fatalf("err: %v", err)
   621  	}
   622  
   623  	check1.InitialStatus = "foo"
   624  	err = check1.validate()
   625  	if err == nil {
   626  		t.Fatal("Expected an error")
   627  	}
   628  
   629  	if !strings.Contains(err.Error(), "invalid initial check state (foo)") {
   630  		t.Fatalf("err: %v", err)
   631  	}
   632  
   633  	check1.InitialStatus = api.HealthCritical
   634  	err = check1.validate()
   635  	if err != nil {
   636  		t.Fatalf("err: %v", err)
   637  	}
   638  
   639  	check1.InitialStatus = api.HealthPassing
   640  	err = check1.validate()
   641  	if err != nil {
   642  		t.Fatalf("err: %v", err)
   643  	}
   644  
   645  	check1.InitialStatus = ""
   646  	err = check1.validate()
   647  	if err != nil {
   648  		t.Fatalf("err: %v", err)
   649  	}
   650  }
   651  
   652  func TestTask_Validate_LogConfig(t *testing.T) {
   653  	task := &Task{
   654  		LogConfig: DefaultLogConfig(),
   655  	}
   656  	ephemeralDisk := &EphemeralDisk{
   657  		SizeMB: 1,
   658  	}
   659  
   660  	err := task.Validate(ephemeralDisk)
   661  	mErr := err.(*multierror.Error)
   662  	if !strings.Contains(mErr.Errors[3].Error(), "log storage") {
   663  		t.Fatalf("err: %s", err)
   664  	}
   665  }
   666  
   667  func TestTask_Validate_Template(t *testing.T) {
   668  
   669  	bad := &Template{}
   670  	task := &Task{
   671  		Templates: []*Template{bad},
   672  	}
   673  	ephemeralDisk := &EphemeralDisk{
   674  		SizeMB: 1,
   675  	}
   676  
   677  	err := task.Validate(ephemeralDisk)
   678  	if !strings.Contains(err.Error(), "Template 1 validation failed") {
   679  		t.Fatalf("err: %s", err)
   680  	}
   681  
   682  	// Have two templates that share the same destination
   683  	good := &Template{
   684  		SourcePath: "foo",
   685  		DestPath:   "local/foo",
   686  		ChangeMode: "noop",
   687  	}
   688  
   689  	task.Templates = []*Template{good, good}
   690  	err = task.Validate(ephemeralDisk)
   691  	if !strings.Contains(err.Error(), "same destination as") {
   692  		t.Fatalf("err: %s", err)
   693  	}
   694  }
   695  
   696  func TestTemplate_Validate(t *testing.T) {
   697  	cases := []struct {
   698  		Tmpl         *Template
   699  		Fail         bool
   700  		ContainsErrs []string
   701  	}{
   702  		{
   703  			Tmpl: &Template{},
   704  			Fail: true,
   705  			ContainsErrs: []string{
   706  				"specify a source path",
   707  				"specify a destination",
   708  				TemplateChangeModeInvalidError.Error(),
   709  			},
   710  		},
   711  		{
   712  			Tmpl: &Template{
   713  				Splay: -100,
   714  			},
   715  			Fail: true,
   716  			ContainsErrs: []string{
   717  				"positive splay",
   718  			},
   719  		},
   720  		{
   721  			Tmpl: &Template{
   722  				ChangeMode: "foo",
   723  			},
   724  			Fail: true,
   725  			ContainsErrs: []string{
   726  				TemplateChangeModeInvalidError.Error(),
   727  			},
   728  		},
   729  		{
   730  			Tmpl: &Template{
   731  				ChangeMode: "signal",
   732  			},
   733  			Fail: true,
   734  			ContainsErrs: []string{
   735  				"specify signal value",
   736  			},
   737  		},
   738  		{
   739  			Tmpl: &Template{
   740  				SourcePath: "foo",
   741  				DestPath:   "../../root",
   742  				ChangeMode: "noop",
   743  			},
   744  			Fail: true,
   745  			ContainsErrs: []string{
   746  				"destination escapes",
   747  			},
   748  		},
   749  		{
   750  			Tmpl: &Template{
   751  				SourcePath: "foo",
   752  				DestPath:   "local/foo",
   753  				ChangeMode: "noop",
   754  			},
   755  			Fail: false,
   756  		},
   757  		{
   758  			Tmpl: &Template{
   759  				SourcePath: "foo",
   760  				DestPath:   "local/foo",
   761  				ChangeMode: "noop",
   762  				Perms:      "0444",
   763  			},
   764  			Fail: false,
   765  		},
   766  		{
   767  			Tmpl: &Template{
   768  				SourcePath: "foo",
   769  				DestPath:   "local/foo",
   770  				ChangeMode: "noop",
   771  				Perms:      "zza",
   772  			},
   773  			Fail: true,
   774  			ContainsErrs: []string{
   775  				"as octal",
   776  			},
   777  		},
   778  	}
   779  
   780  	for i, c := range cases {
   781  		err := c.Tmpl.Validate()
   782  		if err != nil {
   783  			if !c.Fail {
   784  				t.Fatalf("Case %d: shouldn't have failed: %v", i+1, err)
   785  			}
   786  
   787  			e := err.Error()
   788  			for _, exp := range c.ContainsErrs {
   789  				if !strings.Contains(e, exp) {
   790  					t.Fatalf("Cased %d: should have contained error %q: %q", i+1, exp, e)
   791  				}
   792  			}
   793  		} else if c.Fail {
   794  			t.Fatalf("Case %d: should have failed: %v", i+1, err)
   795  		}
   796  	}
   797  }
   798  
   799  func TestConstraint_Validate(t *testing.T) {
   800  	c := &Constraint{}
   801  	err := c.Validate()
   802  	mErr := err.(*multierror.Error)
   803  	if !strings.Contains(mErr.Errors[0].Error(), "Missing constraint operand") {
   804  		t.Fatalf("err: %s", err)
   805  	}
   806  
   807  	c = &Constraint{
   808  		LTarget: "$attr.kernel.name",
   809  		RTarget: "linux",
   810  		Operand: "=",
   811  	}
   812  	err = c.Validate()
   813  	if err != nil {
   814  		t.Fatalf("err: %v", err)
   815  	}
   816  
   817  	// Perform additional regexp validation
   818  	c.Operand = ConstraintRegex
   819  	c.RTarget = "(foo"
   820  	err = c.Validate()
   821  	mErr = err.(*multierror.Error)
   822  	if !strings.Contains(mErr.Errors[0].Error(), "missing closing") {
   823  		t.Fatalf("err: %s", err)
   824  	}
   825  
   826  	// Perform version validation
   827  	c.Operand = ConstraintVersion
   828  	c.RTarget = "~> foo"
   829  	err = c.Validate()
   830  	mErr = err.(*multierror.Error)
   831  	if !strings.Contains(mErr.Errors[0].Error(), "Malformed constraint") {
   832  		t.Fatalf("err: %s", err)
   833  	}
   834  }
   835  
   836  func TestResource_NetIndex(t *testing.T) {
   837  	r := &Resources{
   838  		Networks: []*NetworkResource{
   839  			&NetworkResource{Device: "eth0"},
   840  			&NetworkResource{Device: "lo0"},
   841  			&NetworkResource{Device: ""},
   842  		},
   843  	}
   844  	if idx := r.NetIndex(&NetworkResource{Device: "eth0"}); idx != 0 {
   845  		t.Fatalf("Bad: %d", idx)
   846  	}
   847  	if idx := r.NetIndex(&NetworkResource{Device: "lo0"}); idx != 1 {
   848  		t.Fatalf("Bad: %d", idx)
   849  	}
   850  	if idx := r.NetIndex(&NetworkResource{Device: "eth1"}); idx != -1 {
   851  		t.Fatalf("Bad: %d", idx)
   852  	}
   853  }
   854  
   855  func TestResource_Superset(t *testing.T) {
   856  	r1 := &Resources{
   857  		CPU:      2000,
   858  		MemoryMB: 2048,
   859  		DiskMB:   10000,
   860  		IOPS:     100,
   861  	}
   862  	r2 := &Resources{
   863  		CPU:      2000,
   864  		MemoryMB: 1024,
   865  		DiskMB:   5000,
   866  		IOPS:     50,
   867  	}
   868  
   869  	if s, _ := r1.Superset(r1); !s {
   870  		t.Fatalf("bad")
   871  	}
   872  	if s, _ := r1.Superset(r2); !s {
   873  		t.Fatalf("bad")
   874  	}
   875  	if s, _ := r2.Superset(r1); s {
   876  		t.Fatalf("bad")
   877  	}
   878  	if s, _ := r2.Superset(r2); !s {
   879  		t.Fatalf("bad")
   880  	}
   881  }
   882  
   883  func TestResource_Add(t *testing.T) {
   884  	r1 := &Resources{
   885  		CPU:      2000,
   886  		MemoryMB: 2048,
   887  		DiskMB:   10000,
   888  		IOPS:     100,
   889  		Networks: []*NetworkResource{
   890  			&NetworkResource{
   891  				CIDR:          "10.0.0.0/8",
   892  				MBits:         100,
   893  				ReservedPorts: []Port{{"ssh", 22}},
   894  			},
   895  		},
   896  	}
   897  	r2 := &Resources{
   898  		CPU:      2000,
   899  		MemoryMB: 1024,
   900  		DiskMB:   5000,
   901  		IOPS:     50,
   902  		Networks: []*NetworkResource{
   903  			&NetworkResource{
   904  				IP:            "10.0.0.1",
   905  				MBits:         50,
   906  				ReservedPorts: []Port{{"web", 80}},
   907  			},
   908  		},
   909  	}
   910  
   911  	err := r1.Add(r2)
   912  	if err != nil {
   913  		t.Fatalf("Err: %v", err)
   914  	}
   915  
   916  	expect := &Resources{
   917  		CPU:      3000,
   918  		MemoryMB: 3072,
   919  		DiskMB:   15000,
   920  		IOPS:     150,
   921  		Networks: []*NetworkResource{
   922  			&NetworkResource{
   923  				CIDR:          "10.0.0.0/8",
   924  				MBits:         150,
   925  				ReservedPorts: []Port{{"ssh", 22}, {"web", 80}},
   926  			},
   927  		},
   928  	}
   929  
   930  	if !reflect.DeepEqual(expect.Networks, r1.Networks) {
   931  		t.Fatalf("bad: %#v %#v", expect, r1)
   932  	}
   933  }
   934  
   935  func TestResource_Add_Network(t *testing.T) {
   936  	r1 := &Resources{}
   937  	r2 := &Resources{
   938  		Networks: []*NetworkResource{
   939  			&NetworkResource{
   940  				MBits:        50,
   941  				DynamicPorts: []Port{{"http", 0}, {"https", 0}},
   942  			},
   943  		},
   944  	}
   945  	r3 := &Resources{
   946  		Networks: []*NetworkResource{
   947  			&NetworkResource{
   948  				MBits:        25,
   949  				DynamicPorts: []Port{{"admin", 0}},
   950  			},
   951  		},
   952  	}
   953  
   954  	err := r1.Add(r2)
   955  	if err != nil {
   956  		t.Fatalf("Err: %v", err)
   957  	}
   958  	err = r1.Add(r3)
   959  	if err != nil {
   960  		t.Fatalf("Err: %v", err)
   961  	}
   962  
   963  	expect := &Resources{
   964  		Networks: []*NetworkResource{
   965  			&NetworkResource{
   966  				MBits:        75,
   967  				DynamicPorts: []Port{{"http", 0}, {"https", 0}, {"admin", 0}},
   968  			},
   969  		},
   970  	}
   971  
   972  	if !reflect.DeepEqual(expect.Networks, r1.Networks) {
   973  		t.Fatalf("bad: %#v %#v", expect.Networks[0], r1.Networks[0])
   974  	}
   975  }
   976  
   977  func TestEncodeDecode(t *testing.T) {
   978  	type FooRequest struct {
   979  		Foo string
   980  		Bar int
   981  		Baz bool
   982  	}
   983  	arg := &FooRequest{
   984  		Foo: "test",
   985  		Bar: 42,
   986  		Baz: true,
   987  	}
   988  	buf, err := Encode(1, arg)
   989  	if err != nil {
   990  		t.Fatalf("err: %v", err)
   991  	}
   992  
   993  	var out FooRequest
   994  	err = Decode(buf[1:], &out)
   995  	if err != nil {
   996  		t.Fatalf("err: %v", err)
   997  	}
   998  
   999  	if !reflect.DeepEqual(arg, &out) {
  1000  		t.Fatalf("bad: %#v %#v", arg, out)
  1001  	}
  1002  }
  1003  
  1004  func BenchmarkEncodeDecode(b *testing.B) {
  1005  	job := testJob()
  1006  
  1007  	for i := 0; i < b.N; i++ {
  1008  		buf, err := Encode(1, job)
  1009  		if err != nil {
  1010  			b.Fatalf("err: %v", err)
  1011  		}
  1012  
  1013  		var out Job
  1014  		err = Decode(buf[1:], &out)
  1015  		if err != nil {
  1016  			b.Fatalf("err: %v", err)
  1017  		}
  1018  	}
  1019  }
  1020  
  1021  func TestInvalidServiceCheck(t *testing.T) {
  1022  	s := Service{
  1023  		Name:      "service-name",
  1024  		PortLabel: "bar",
  1025  		Checks: []*ServiceCheck{
  1026  			{
  1027  				Name: "check-name",
  1028  				Type: "lol",
  1029  			},
  1030  		},
  1031  	}
  1032  	if err := s.Validate(); err == nil {
  1033  		t.Fatalf("Service should be invalid (invalid type)")
  1034  	}
  1035  
  1036  	s = Service{
  1037  		Name:      "service.name",
  1038  		PortLabel: "bar",
  1039  	}
  1040  	if err := s.ValidateName(s.Name); err == nil {
  1041  		t.Fatalf("Service should be invalid (contains a dot): %v", err)
  1042  	}
  1043  
  1044  	s = Service{
  1045  		Name:      "-my-service",
  1046  		PortLabel: "bar",
  1047  	}
  1048  	if err := s.Validate(); err == nil {
  1049  		t.Fatalf("Service should be invalid (begins with a hyphen): %v", err)
  1050  	}
  1051  
  1052  	s = Service{
  1053  		Name:      "my-service-${NOMAD_META_FOO}",
  1054  		PortLabel: "bar",
  1055  	}
  1056  	if err := s.Validate(); err != nil {
  1057  		t.Fatalf("Service should be valid: %v", err)
  1058  	}
  1059  
  1060  	s = Service{
  1061  		Name:      "abcdef0123456789-abcdef0123456789-abcdef0123456789-abcdef0123456",
  1062  		PortLabel: "bar",
  1063  	}
  1064  	if err := s.ValidateName(s.Name); err == nil {
  1065  		t.Fatalf("Service should be invalid (too long): %v", err)
  1066  	}
  1067  
  1068  	s = Service{
  1069  		Name: "service-name",
  1070  		Checks: []*ServiceCheck{
  1071  			{
  1072  				Name:     "check-tcp",
  1073  				Type:     ServiceCheckTCP,
  1074  				Interval: 5 * time.Second,
  1075  				Timeout:  2 * time.Second,
  1076  			},
  1077  			{
  1078  				Name:     "check-http",
  1079  				Type:     ServiceCheckHTTP,
  1080  				Path:     "/foo",
  1081  				Interval: 5 * time.Second,
  1082  				Timeout:  2 * time.Second,
  1083  			},
  1084  		},
  1085  	}
  1086  	if err := s.Validate(); err == nil {
  1087  		t.Fatalf("service should be invalid (tcp/http checks with no port): %v", err)
  1088  	}
  1089  
  1090  	s = Service{
  1091  		Name: "service-name",
  1092  		Checks: []*ServiceCheck{
  1093  			{
  1094  				Name:     "check-script",
  1095  				Type:     ServiceCheckScript,
  1096  				Command:  "/bin/date",
  1097  				Interval: 5 * time.Second,
  1098  				Timeout:  2 * time.Second,
  1099  			},
  1100  		},
  1101  	}
  1102  	if err := s.Validate(); err != nil {
  1103  		t.Fatalf("un-expected error: %v", err)
  1104  	}
  1105  }
  1106  
  1107  func TestDistinctCheckID(t *testing.T) {
  1108  	c1 := ServiceCheck{
  1109  		Name:     "web-health",
  1110  		Type:     "http",
  1111  		Path:     "/health",
  1112  		Interval: 2 * time.Second,
  1113  		Timeout:  3 * time.Second,
  1114  	}
  1115  	c2 := ServiceCheck{
  1116  		Name:     "web-health",
  1117  		Type:     "http",
  1118  		Path:     "/health1",
  1119  		Interval: 2 * time.Second,
  1120  		Timeout:  3 * time.Second,
  1121  	}
  1122  
  1123  	c3 := ServiceCheck{
  1124  		Name:     "web-health",
  1125  		Type:     "http",
  1126  		Path:     "/health",
  1127  		Interval: 4 * time.Second,
  1128  		Timeout:  3 * time.Second,
  1129  	}
  1130  	serviceID := "123"
  1131  	c1Hash := c1.Hash(serviceID)
  1132  	c2Hash := c2.Hash(serviceID)
  1133  	c3Hash := c3.Hash(serviceID)
  1134  
  1135  	if c1Hash == c2Hash || c1Hash == c3Hash || c3Hash == c2Hash {
  1136  		t.Fatalf("Checks need to be uniq c1: %s, c2: %s, c3: %s", c1Hash, c2Hash, c3Hash)
  1137  	}
  1138  
  1139  }
  1140  
  1141  func TestService_Canonicalize(t *testing.T) {
  1142  	job := "example"
  1143  	taskGroup := "cache"
  1144  	task := "redis"
  1145  
  1146  	s := Service{
  1147  		Name: "${TASK}-db",
  1148  	}
  1149  
  1150  	s.Canonicalize(job, taskGroup, task)
  1151  	if s.Name != "redis-db" {
  1152  		t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name)
  1153  	}
  1154  
  1155  	s.Name = "db"
  1156  	s.Canonicalize(job, taskGroup, task)
  1157  	if s.Name != "db" {
  1158  		t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name)
  1159  	}
  1160  
  1161  	s.Name = "${JOB}-${TASKGROUP}-${TASK}-db"
  1162  	s.Canonicalize(job, taskGroup, task)
  1163  	if s.Name != "example-cache-redis-db" {
  1164  		t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name)
  1165  	}
  1166  
  1167  	s.Name = "${BASE}-db"
  1168  	s.Canonicalize(job, taskGroup, task)
  1169  	if s.Name != "example-cache-redis-db" {
  1170  		t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name)
  1171  	}
  1172  
  1173  }
  1174  
  1175  func TestJob_ExpandServiceNames(t *testing.T) {
  1176  	j := &Job{
  1177  		Name: "my-job",
  1178  		TaskGroups: []*TaskGroup{
  1179  			&TaskGroup{
  1180  				Name: "web",
  1181  				Tasks: []*Task{
  1182  					{
  1183  						Name: "frontend",
  1184  						Services: []*Service{
  1185  							{
  1186  								Name: "${BASE}-default",
  1187  							},
  1188  							{
  1189  								Name: "jmx",
  1190  							},
  1191  						},
  1192  					},
  1193  				},
  1194  			},
  1195  			&TaskGroup{
  1196  				Name: "admin",
  1197  				Tasks: []*Task{
  1198  					{
  1199  						Name: "admin-web",
  1200  					},
  1201  				},
  1202  			},
  1203  		},
  1204  	}
  1205  
  1206  	j.Canonicalize()
  1207  
  1208  	service1Name := j.TaskGroups[0].Tasks[0].Services[0].Name
  1209  	if service1Name != "my-job-web-frontend-default" {
  1210  		t.Fatalf("Expected Service Name: %s, Actual: %s", "my-job-web-frontend-default", service1Name)
  1211  	}
  1212  
  1213  	service2Name := j.TaskGroups[0].Tasks[0].Services[1].Name
  1214  	if service2Name != "jmx" {
  1215  		t.Fatalf("Expected Service Name: %s, Actual: %s", "jmx", service2Name)
  1216  	}
  1217  
  1218  }
  1219  
  1220  func TestPeriodicConfig_EnabledInvalid(t *testing.T) {
  1221  	// Create a config that is enabled but with no interval specified.
  1222  	p := &PeriodicConfig{Enabled: true}
  1223  	if err := p.Validate(); err == nil {
  1224  		t.Fatal("Enabled PeriodicConfig with no spec or type shouldn't be valid")
  1225  	}
  1226  
  1227  	// Create a config that is enabled, with a spec but no type specified.
  1228  	p = &PeriodicConfig{Enabled: true, Spec: "foo"}
  1229  	if err := p.Validate(); err == nil {
  1230  		t.Fatal("Enabled PeriodicConfig with no spec type shouldn't be valid")
  1231  	}
  1232  
  1233  	// Create a config that is enabled, with a spec type but no spec specified.
  1234  	p = &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron}
  1235  	if err := p.Validate(); err == nil {
  1236  		t.Fatal("Enabled PeriodicConfig with no spec shouldn't be valid")
  1237  	}
  1238  
  1239  	// Create a config that is enabled, with a bad time zone.
  1240  	p = &PeriodicConfig{Enabled: true, TimeZone: "FOO"}
  1241  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "time zone") {
  1242  		t.Fatalf("Enabled PeriodicConfig with bad time zone shouldn't be valid: %v", err)
  1243  	}
  1244  }
  1245  
  1246  func TestPeriodicConfig_InvalidCron(t *testing.T) {
  1247  	specs := []string{"foo", "* *", "@foo"}
  1248  	for _, spec := range specs {
  1249  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
  1250  		p.Canonicalize()
  1251  		if err := p.Validate(); err == nil {
  1252  			t.Fatal("Invalid cron spec")
  1253  		}
  1254  	}
  1255  }
  1256  
  1257  func TestPeriodicConfig_ValidCron(t *testing.T) {
  1258  	specs := []string{"0 0 29 2 *", "@hourly", "0 0-15 * * *"}
  1259  	for _, spec := range specs {
  1260  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
  1261  		p.Canonicalize()
  1262  		if err := p.Validate(); err != nil {
  1263  			t.Fatal("Passed valid cron")
  1264  		}
  1265  	}
  1266  }
  1267  
  1268  func TestPeriodicConfig_NextCron(t *testing.T) {
  1269  	from := time.Date(2009, time.November, 10, 23, 22, 30, 0, time.UTC)
  1270  	specs := []string{"0 0 29 2 * 1980", "*/5 * * * *"}
  1271  	expected := []time.Time{time.Time{}, time.Date(2009, time.November, 10, 23, 25, 0, 0, time.UTC)}
  1272  	for i, spec := range specs {
  1273  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
  1274  		p.Canonicalize()
  1275  		n := p.Next(from)
  1276  		if expected[i] != n {
  1277  			t.Fatalf("Next(%v) returned %v; want %v", from, n, expected[i])
  1278  		}
  1279  	}
  1280  }
  1281  
  1282  func TestPeriodicConfig_ValidTimeZone(t *testing.T) {
  1283  	zones := []string{"Africa/Abidjan", "America/Chicago", "Europe/Minsk", "UTC"}
  1284  	for _, zone := range zones {
  1285  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: "0 0 29 2 * 1980", TimeZone: zone}
  1286  		p.Canonicalize()
  1287  		if err := p.Validate(); err != nil {
  1288  			t.Fatalf("Valid tz errored: %v", err)
  1289  		}
  1290  	}
  1291  }
  1292  
  1293  func TestPeriodicConfig_DST(t *testing.T) {
  1294  	// On Sun, Mar 12, 2:00 am 2017: +1 hour UTC
  1295  	p := &PeriodicConfig{
  1296  		Enabled:  true,
  1297  		SpecType: PeriodicSpecCron,
  1298  		Spec:     "0 2 11-12 3 * 2017",
  1299  		TimeZone: "America/Los_Angeles",
  1300  	}
  1301  	p.Canonicalize()
  1302  
  1303  	t1 := time.Date(2017, time.March, 11, 1, 0, 0, 0, p.location)
  1304  	t2 := time.Date(2017, time.March, 12, 1, 0, 0, 0, p.location)
  1305  
  1306  	// E1 is an 8 hour adjustment, E2 is a 7 hour adjustment
  1307  	e1 := time.Date(2017, time.March, 11, 10, 0, 0, 0, time.UTC)
  1308  	e2 := time.Date(2017, time.March, 12, 9, 0, 0, 0, time.UTC)
  1309  
  1310  	n1 := p.Next(t1).UTC()
  1311  	n2 := p.Next(t2).UTC()
  1312  
  1313  	if !reflect.DeepEqual(e1, n1) {
  1314  		t.Fatalf("Got %v; want %v", n1, e1)
  1315  	}
  1316  	if !reflect.DeepEqual(e2, n2) {
  1317  		t.Fatalf("Got %v; want %v", n1, e1)
  1318  	}
  1319  }
  1320  
  1321  func TestRestartPolicy_Validate(t *testing.T) {
  1322  	// Policy with acceptable restart options passes
  1323  	p := &RestartPolicy{
  1324  		Mode:     RestartPolicyModeFail,
  1325  		Attempts: 0,
  1326  		Interval: 5 * time.Second,
  1327  	}
  1328  	if err := p.Validate(); err != nil {
  1329  		t.Fatalf("err: %v", err)
  1330  	}
  1331  
  1332  	// Policy with ambiguous restart options fails
  1333  	p = &RestartPolicy{
  1334  		Mode:     RestartPolicyModeDelay,
  1335  		Attempts: 0,
  1336  		Interval: 5 * time.Second,
  1337  	}
  1338  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "ambiguous") {
  1339  		t.Fatalf("expect ambiguity error, got: %v", err)
  1340  	}
  1341  
  1342  	// Bad policy mode fails
  1343  	p = &RestartPolicy{
  1344  		Mode:     "nope",
  1345  		Attempts: 1,
  1346  		Interval: 5 * time.Second,
  1347  	}
  1348  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "mode") {
  1349  		t.Fatalf("expect mode error, got: %v", err)
  1350  	}
  1351  
  1352  	// Fails when attempts*delay does not fit inside interval
  1353  	p = &RestartPolicy{
  1354  		Mode:     RestartPolicyModeDelay,
  1355  		Attempts: 3,
  1356  		Delay:    5 * time.Second,
  1357  		Interval: 5 * time.Second,
  1358  	}
  1359  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "can't restart") {
  1360  		t.Fatalf("expect restart interval error, got: %v", err)
  1361  	}
  1362  
  1363  	// Fails when interval is to small
  1364  	p = &RestartPolicy{
  1365  		Mode:     RestartPolicyModeDelay,
  1366  		Attempts: 3,
  1367  		Delay:    5 * time.Second,
  1368  		Interval: 2 * time.Second,
  1369  	}
  1370  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "Interval can not be less than") {
  1371  		t.Fatalf("expect interval too small error, got: %v", err)
  1372  	}
  1373  }
  1374  
  1375  func TestAllocation_Index(t *testing.T) {
  1376  	a1 := Allocation{Name: "example.cache[0]"}
  1377  	e1 := 0
  1378  	a2 := Allocation{Name: "ex[123]am123ple.c311ac[123]he12[1][77]"}
  1379  	e2 := 77
  1380  
  1381  	if a1.Index() != e1 || a2.Index() != e2 {
  1382  		t.Fatal()
  1383  	}
  1384  }
  1385  
  1386  func TestTaskArtifact_Validate_Source(t *testing.T) {
  1387  	valid := &TaskArtifact{GetterSource: "google.com"}
  1388  	if err := valid.Validate(); err != nil {
  1389  		t.Fatalf("unexpected error: %v", err)
  1390  	}
  1391  }
  1392  
  1393  func TestTaskArtifact_Validate_Dest(t *testing.T) {
  1394  	valid := &TaskArtifact{GetterSource: "google.com"}
  1395  	if err := valid.Validate(); err != nil {
  1396  		t.Fatalf("unexpected error: %v", err)
  1397  	}
  1398  
  1399  	valid.RelativeDest = "local/"
  1400  	if err := valid.Validate(); err != nil {
  1401  		t.Fatalf("unexpected error: %v", err)
  1402  	}
  1403  
  1404  	valid.RelativeDest = "local/.."
  1405  	if err := valid.Validate(); err != nil {
  1406  		t.Fatalf("unexpected error: %v", err)
  1407  	}
  1408  
  1409  	valid.RelativeDest = "local/../../.."
  1410  	if err := valid.Validate(); err == nil {
  1411  		t.Fatalf("expected error: %v", err)
  1412  	}
  1413  }
  1414  
  1415  func TestAllocation_ShouldMigrate(t *testing.T) {
  1416  	alloc := Allocation{
  1417  		TaskGroup: "foo",
  1418  		Job: &Job{
  1419  			TaskGroups: []*TaskGroup{
  1420  				{
  1421  					Name: "foo",
  1422  					EphemeralDisk: &EphemeralDisk{
  1423  						Migrate: true,
  1424  						Sticky:  true,
  1425  					},
  1426  				},
  1427  			},
  1428  		},
  1429  	}
  1430  
  1431  	if !alloc.ShouldMigrate() {
  1432  		t.Fatalf("bad: %v", alloc)
  1433  	}
  1434  
  1435  	alloc1 := Allocation{
  1436  		TaskGroup: "foo",
  1437  		Job: &Job{
  1438  			TaskGroups: []*TaskGroup{
  1439  				{
  1440  					Name:          "foo",
  1441  					EphemeralDisk: &EphemeralDisk{},
  1442  				},
  1443  			},
  1444  		},
  1445  	}
  1446  
  1447  	if alloc1.ShouldMigrate() {
  1448  		t.Fatalf("bad: %v", alloc)
  1449  	}
  1450  
  1451  	alloc2 := Allocation{
  1452  		TaskGroup: "foo",
  1453  		Job: &Job{
  1454  			TaskGroups: []*TaskGroup{
  1455  				{
  1456  					Name: "foo",
  1457  					EphemeralDisk: &EphemeralDisk{
  1458  						Sticky:  false,
  1459  						Migrate: true,
  1460  					},
  1461  				},
  1462  			},
  1463  		},
  1464  	}
  1465  
  1466  	if alloc2.ShouldMigrate() {
  1467  		t.Fatalf("bad: %v", alloc)
  1468  	}
  1469  
  1470  	alloc3 := Allocation{
  1471  		TaskGroup: "foo",
  1472  		Job: &Job{
  1473  			TaskGroups: []*TaskGroup{
  1474  				{
  1475  					Name: "foo",
  1476  				},
  1477  			},
  1478  		},
  1479  	}
  1480  
  1481  	if alloc3.ShouldMigrate() {
  1482  		t.Fatalf("bad: %v", alloc)
  1483  	}
  1484  }
  1485  
  1486  func TestTaskArtifact_Validate_Checksum(t *testing.T) {
  1487  	cases := []struct {
  1488  		Input *TaskArtifact
  1489  		Err   bool
  1490  	}{
  1491  		{
  1492  			&TaskArtifact{
  1493  				GetterSource: "foo.com",
  1494  				GetterOptions: map[string]string{
  1495  					"checksum": "no-type",
  1496  				},
  1497  			},
  1498  			true,
  1499  		},
  1500  		{
  1501  			&TaskArtifact{
  1502  				GetterSource: "foo.com",
  1503  				GetterOptions: map[string]string{
  1504  					"checksum": "md5:toosmall",
  1505  				},
  1506  			},
  1507  			true,
  1508  		},
  1509  		{
  1510  			&TaskArtifact{
  1511  				GetterSource: "foo.com",
  1512  				GetterOptions: map[string]string{
  1513  					"checksum": "invalid:type",
  1514  				},
  1515  			},
  1516  			true,
  1517  		},
  1518  	}
  1519  
  1520  	for i, tc := range cases {
  1521  		err := tc.Input.Validate()
  1522  		if (err != nil) != tc.Err {
  1523  			t.Fatalf("case %d: %v", i, err)
  1524  			continue
  1525  		}
  1526  	}
  1527  }
  1528  
  1529  func TestAllocation_Terminated(t *testing.T) {
  1530  	type desiredState struct {
  1531  		ClientStatus  string
  1532  		DesiredStatus string
  1533  		Terminated    bool
  1534  	}
  1535  
  1536  	harness := []desiredState{
  1537  		{
  1538  			ClientStatus:  AllocClientStatusPending,
  1539  			DesiredStatus: AllocDesiredStatusStop,
  1540  			Terminated:    false,
  1541  		},
  1542  		{
  1543  			ClientStatus:  AllocClientStatusRunning,
  1544  			DesiredStatus: AllocDesiredStatusStop,
  1545  			Terminated:    false,
  1546  		},
  1547  		{
  1548  			ClientStatus:  AllocClientStatusFailed,
  1549  			DesiredStatus: AllocDesiredStatusStop,
  1550  			Terminated:    true,
  1551  		},
  1552  		{
  1553  			ClientStatus:  AllocClientStatusFailed,
  1554  			DesiredStatus: AllocDesiredStatusRun,
  1555  			Terminated:    true,
  1556  		},
  1557  	}
  1558  
  1559  	for _, state := range harness {
  1560  		alloc := Allocation{}
  1561  		alloc.DesiredStatus = state.DesiredStatus
  1562  		alloc.ClientStatus = state.ClientStatus
  1563  		if alloc.Terminated() != state.Terminated {
  1564  			t.Fatalf("expected: %v, actual: %v", state.Terminated, alloc.Terminated())
  1565  		}
  1566  	}
  1567  }
  1568  
  1569  func TestVault_Validate(t *testing.T) {
  1570  	v := &Vault{
  1571  		Env:        true,
  1572  		ChangeMode: VaultChangeModeNoop,
  1573  	}
  1574  
  1575  	if err := v.Validate(); err == nil || !strings.Contains(err.Error(), "Policy list") {
  1576  		t.Fatalf("Expected policy list empty error")
  1577  	}
  1578  
  1579  	v.Policies = []string{"foo", "root"}
  1580  	v.ChangeMode = VaultChangeModeSignal
  1581  
  1582  	err := v.Validate()
  1583  	if err == nil {
  1584  		t.Fatalf("Expected validation errors")
  1585  	}
  1586  
  1587  	if !strings.Contains(err.Error(), "Signal must") {
  1588  		t.Fatalf("Expected signal empty error")
  1589  	}
  1590  	if !strings.Contains(err.Error(), "root") {
  1591  		t.Fatalf("Expected root error")
  1592  	}
  1593  }
  1594  
  1595  func TestParameterizedJobConfig_Validate(t *testing.T) {
  1596  	d := &ParameterizedJobConfig{
  1597  		Payload: "foo",
  1598  	}
  1599  
  1600  	if err := d.Validate(); err == nil || !strings.Contains(err.Error(), "payload") {
  1601  		t.Fatalf("Expected unknown payload requirement: %v", err)
  1602  	}
  1603  
  1604  	d.Payload = DispatchPayloadOptional
  1605  	d.MetaOptional = []string{"foo", "bar"}
  1606  	d.MetaRequired = []string{"bar", "baz"}
  1607  
  1608  	if err := d.Validate(); err == nil || !strings.Contains(err.Error(), "disjoint") {
  1609  		t.Fatalf("Expected meta not being disjoint error: %v", err)
  1610  	}
  1611  }
  1612  
  1613  func TestParameterizedJobConfig_Validate_NonBatch(t *testing.T) {
  1614  	job := testJob()
  1615  	job.ParameterizedJob = &ParameterizedJobConfig{
  1616  		Payload: DispatchPayloadOptional,
  1617  	}
  1618  	job.Type = JobTypeSystem
  1619  
  1620  	if err := job.Validate(); err == nil || !strings.Contains(err.Error(), "only be used with") {
  1621  		t.Fatalf("Expected bad scheduler tpye: %v", err)
  1622  	}
  1623  }
  1624  
  1625  func TestParameterizedJobConfig_Canonicalize(t *testing.T) {
  1626  	d := &ParameterizedJobConfig{}
  1627  	d.Canonicalize()
  1628  	if d.Payload != DispatchPayloadOptional {
  1629  		t.Fatalf("Canonicalize failed")
  1630  	}
  1631  }
  1632  
  1633  func TestDispatchPayloadConfig_Validate(t *testing.T) {
  1634  	d := &DispatchPayloadConfig{
  1635  		File: "foo",
  1636  	}
  1637  
  1638  	// task/local/haha
  1639  	if err := d.Validate(); err != nil {
  1640  		t.Fatalf("bad: %v", err)
  1641  	}
  1642  
  1643  	// task/haha
  1644  	d.File = "../haha"
  1645  	if err := d.Validate(); err != nil {
  1646  		t.Fatalf("bad: %v", err)
  1647  	}
  1648  
  1649  	// ../haha
  1650  	d.File = "../../../haha"
  1651  	if err := d.Validate(); err == nil {
  1652  		t.Fatalf("bad: %v", err)
  1653  	}
  1654  }
  1655  
  1656  func TestIsRecoverable(t *testing.T) {
  1657  	if IsRecoverable(nil) {
  1658  		t.Errorf("nil should not be recoverable")
  1659  	}
  1660  	if IsRecoverable(NewRecoverableError(nil, true)) {
  1661  		t.Errorf("NewRecoverableError(nil, true) should not be recoverable")
  1662  	}
  1663  	if IsRecoverable(fmt.Errorf("i promise im recoverable")) {
  1664  		t.Errorf("Custom errors should not be recoverable")
  1665  	}
  1666  	if IsRecoverable(NewRecoverableError(fmt.Errorf(""), false)) {
  1667  		t.Errorf("Explicitly unrecoverable errors should not be recoverable")
  1668  	}
  1669  	if !IsRecoverable(NewRecoverableError(fmt.Errorf(""), true)) {
  1670  		t.Errorf("Explicitly recoverable errors *should* be recoverable")
  1671  	}
  1672  }