github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/nomad/structs/structs_test.go (about)

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