github.com/mattyr/nomad@v0.3.3-0.20160919021406-3485a065154a/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  						ExcludeNomadEnv: false,
   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 TestTaskGroup_Validate(t *testing.T) {
   310  	tg := &TaskGroup{
   311  		Count: -1,
   312  		RestartPolicy: &RestartPolicy{
   313  			Interval: 5 * time.Minute,
   314  			Delay:    10 * time.Second,
   315  			Attempts: 10,
   316  			Mode:     RestartPolicyModeDelay,
   317  		},
   318  	}
   319  	err := tg.Validate()
   320  	mErr := err.(*multierror.Error)
   321  	if !strings.Contains(mErr.Errors[0].Error(), "group name") {
   322  		t.Fatalf("err: %s", err)
   323  	}
   324  	if !strings.Contains(mErr.Errors[1].Error(), "count can't be negative") {
   325  		t.Fatalf("err: %s", err)
   326  	}
   327  	if !strings.Contains(mErr.Errors[2].Error(), "Missing tasks") {
   328  		t.Fatalf("err: %s", err)
   329  	}
   330  
   331  	tg = &TaskGroup{
   332  		Name:  "web",
   333  		Count: 1,
   334  		Tasks: []*Task{
   335  			&Task{Name: "web"},
   336  			&Task{Name: "web"},
   337  			&Task{},
   338  		},
   339  		RestartPolicy: &RestartPolicy{
   340  			Interval: 5 * time.Minute,
   341  			Delay:    10 * time.Second,
   342  			Attempts: 10,
   343  			Mode:     RestartPolicyModeDelay,
   344  		},
   345  	}
   346  
   347  	err = tg.Validate()
   348  	mErr = err.(*multierror.Error)
   349  	if !strings.Contains(mErr.Errors[0].Error(), "should have a local disk object") {
   350  		t.Fatalf("err: %s", err)
   351  	}
   352  	if !strings.Contains(mErr.Errors[1].Error(), "2 redefines 'web' from task 1") {
   353  		t.Fatalf("err: %s", err)
   354  	}
   355  	if !strings.Contains(mErr.Errors[2].Error(), "Task 3 missing name") {
   356  		t.Fatalf("err: %s", err)
   357  	}
   358  	if !strings.Contains(mErr.Errors[3].Error(), "Task web validation failed") {
   359  		t.Fatalf("err: %s", err)
   360  	}
   361  }
   362  
   363  func TestTask_Validate(t *testing.T) {
   364  	task := &Task{}
   365  	ephemeralDisk := DefaultEphemeralDisk()
   366  	err := task.Validate(ephemeralDisk)
   367  	mErr := err.(*multierror.Error)
   368  	if !strings.Contains(mErr.Errors[0].Error(), "task name") {
   369  		t.Fatalf("err: %s", err)
   370  	}
   371  	if !strings.Contains(mErr.Errors[1].Error(), "task driver") {
   372  		t.Fatalf("err: %s", err)
   373  	}
   374  	if !strings.Contains(mErr.Errors[2].Error(), "task resources") {
   375  		t.Fatalf("err: %s", err)
   376  	}
   377  
   378  	task = &Task{Name: "web/foo"}
   379  	err = task.Validate(ephemeralDisk)
   380  	mErr = err.(*multierror.Error)
   381  	if !strings.Contains(mErr.Errors[0].Error(), "slashes") {
   382  		t.Fatalf("err: %s", err)
   383  	}
   384  
   385  	task = &Task{
   386  		Name:   "web",
   387  		Driver: "docker",
   388  		Resources: &Resources{
   389  			CPU:      100,
   390  			MemoryMB: 100,
   391  			IOPS:     10,
   392  		},
   393  		LogConfig: DefaultLogConfig(),
   394  	}
   395  	ephemeralDisk.SizeMB = 200
   396  	err = task.Validate(ephemeralDisk)
   397  	if err != nil {
   398  		t.Fatalf("err: %s", err)
   399  	}
   400  }
   401  
   402  func TestTask_Validate_Services(t *testing.T) {
   403  	s1 := &Service{
   404  		Name:      "service-name",
   405  		PortLabel: "bar",
   406  		Checks: []*ServiceCheck{
   407  			{
   408  				Name:     "check-name",
   409  				Type:     ServiceCheckTCP,
   410  				Interval: 0 * time.Second,
   411  			},
   412  			{
   413  				Name:    "check-name",
   414  				Type:    ServiceCheckTCP,
   415  				Timeout: 2 * time.Second,
   416  			},
   417  		},
   418  	}
   419  
   420  	s2 := &Service{
   421  		Name: "service-name",
   422  	}
   423  
   424  	ephemeralDisk := DefaultEphemeralDisk()
   425  	task := &Task{
   426  		Name:   "web",
   427  		Driver: "docker",
   428  		Resources: &Resources{
   429  			CPU:      100,
   430  			MemoryMB: 100,
   431  			IOPS:     10,
   432  		},
   433  		Services: []*Service{s1, s2},
   434  	}
   435  	ephemeralDisk.SizeMB = 200
   436  
   437  	err := task.Validate(ephemeralDisk)
   438  	if err == nil {
   439  		t.Fatal("expected an error")
   440  	}
   441  	if !strings.Contains(err.Error(), "referenced by services service-name does not exist") {
   442  		t.Fatalf("err: %s", err)
   443  	}
   444  
   445  	if !strings.Contains(err.Error(), "service \"service-name\" is duplicate") {
   446  		t.Fatalf("err: %v", err)
   447  	}
   448  
   449  	if !strings.Contains(err.Error(), "check \"check-name\" is duplicate") {
   450  		t.Fatalf("err: %v", err)
   451  	}
   452  
   453  	if !strings.Contains(err.Error(), "interval (0s) can not be lower") {
   454  		t.Fatalf("err: %v", err)
   455  	}
   456  }
   457  
   458  func TestTask_Validate_Service_Check(t *testing.T) {
   459  
   460  	check1 := ServiceCheck{
   461  		Name:     "check-name",
   462  		Type:     ServiceCheckTCP,
   463  		Interval: 10 * time.Second,
   464  		Timeout:  2 * time.Second,
   465  	}
   466  
   467  	err := check1.validate()
   468  	if err != nil {
   469  		t.Fatal("err: %v", err)
   470  	}
   471  
   472  	check1.InitialStatus = "foo"
   473  	err = check1.validate()
   474  	if err == nil {
   475  		t.Fatal("Expected an error")
   476  	}
   477  
   478  	if !strings.Contains(err.Error(), "invalid initial check state (foo)") {
   479  		t.Fatalf("err: %v", err)
   480  	}
   481  
   482  	check1.InitialStatus = api.HealthCritical
   483  	err = check1.validate()
   484  	if err != nil {
   485  		t.Fatalf("err: %v", err)
   486  	}
   487  
   488  	check1.InitialStatus = api.HealthPassing
   489  	err = check1.validate()
   490  	if err != nil {
   491  		t.Fatalf("err: %v", err)
   492  	}
   493  
   494  	check1.InitialStatus = ""
   495  	err = check1.validate()
   496  	if err != nil {
   497  		t.Fatalf("err: %v", err)
   498  	}
   499  }
   500  
   501  func TestTask_Validate_LogConfig(t *testing.T) {
   502  	task := &Task{
   503  		LogConfig: DefaultLogConfig(),
   504  	}
   505  	ephemeralDisk := &EphemeralDisk{
   506  		SizeMB: 1,
   507  	}
   508  
   509  	err := task.Validate(ephemeralDisk)
   510  	mErr := err.(*multierror.Error)
   511  	if !strings.Contains(mErr.Errors[3].Error(), "log storage") {
   512  		t.Fatalf("err: %s", err)
   513  	}
   514  }
   515  
   516  func TestConstraint_Validate(t *testing.T) {
   517  	c := &Constraint{}
   518  	err := c.Validate()
   519  	mErr := err.(*multierror.Error)
   520  	if !strings.Contains(mErr.Errors[0].Error(), "Missing constraint operand") {
   521  		t.Fatalf("err: %s", err)
   522  	}
   523  
   524  	c = &Constraint{
   525  		LTarget: "$attr.kernel.name",
   526  		RTarget: "linux",
   527  		Operand: "=",
   528  	}
   529  	err = c.Validate()
   530  	if err != nil {
   531  		t.Fatalf("err: %v", err)
   532  	}
   533  
   534  	// Perform additional regexp validation
   535  	c.Operand = ConstraintRegex
   536  	c.RTarget = "(foo"
   537  	err = c.Validate()
   538  	mErr = err.(*multierror.Error)
   539  	if !strings.Contains(mErr.Errors[0].Error(), "missing closing") {
   540  		t.Fatalf("err: %s", err)
   541  	}
   542  
   543  	// Perform version validation
   544  	c.Operand = ConstraintVersion
   545  	c.RTarget = "~> foo"
   546  	err = c.Validate()
   547  	mErr = err.(*multierror.Error)
   548  	if !strings.Contains(mErr.Errors[0].Error(), "Malformed constraint") {
   549  		t.Fatalf("err: %s", err)
   550  	}
   551  }
   552  
   553  func TestResource_NetIndex(t *testing.T) {
   554  	r := &Resources{
   555  		Networks: []*NetworkResource{
   556  			&NetworkResource{Device: "eth0"},
   557  			&NetworkResource{Device: "lo0"},
   558  			&NetworkResource{Device: ""},
   559  		},
   560  	}
   561  	if idx := r.NetIndex(&NetworkResource{Device: "eth0"}); idx != 0 {
   562  		t.Fatalf("Bad: %d", idx)
   563  	}
   564  	if idx := r.NetIndex(&NetworkResource{Device: "lo0"}); idx != 1 {
   565  		t.Fatalf("Bad: %d", idx)
   566  	}
   567  	if idx := r.NetIndex(&NetworkResource{Device: "eth1"}); idx != -1 {
   568  		t.Fatalf("Bad: %d", idx)
   569  	}
   570  }
   571  
   572  func TestResource_Superset(t *testing.T) {
   573  	r1 := &Resources{
   574  		CPU:      2000,
   575  		MemoryMB: 2048,
   576  		DiskMB:   10000,
   577  		IOPS:     100,
   578  	}
   579  	r2 := &Resources{
   580  		CPU:      2000,
   581  		MemoryMB: 1024,
   582  		DiskMB:   5000,
   583  		IOPS:     50,
   584  	}
   585  
   586  	if s, _ := r1.Superset(r1); !s {
   587  		t.Fatalf("bad")
   588  	}
   589  	if s, _ := r1.Superset(r2); !s {
   590  		t.Fatalf("bad")
   591  	}
   592  	if s, _ := r2.Superset(r1); s {
   593  		t.Fatalf("bad")
   594  	}
   595  	if s, _ := r2.Superset(r2); !s {
   596  		t.Fatalf("bad")
   597  	}
   598  }
   599  
   600  func TestResource_Add(t *testing.T) {
   601  	r1 := &Resources{
   602  		CPU:      2000,
   603  		MemoryMB: 2048,
   604  		DiskMB:   10000,
   605  		IOPS:     100,
   606  		Networks: []*NetworkResource{
   607  			&NetworkResource{
   608  				CIDR:          "10.0.0.0/8",
   609  				MBits:         100,
   610  				ReservedPorts: []Port{{"ssh", 22}},
   611  			},
   612  		},
   613  	}
   614  	r2 := &Resources{
   615  		CPU:      2000,
   616  		MemoryMB: 1024,
   617  		DiskMB:   5000,
   618  		IOPS:     50,
   619  		Networks: []*NetworkResource{
   620  			&NetworkResource{
   621  				IP:            "10.0.0.1",
   622  				MBits:         50,
   623  				ReservedPorts: []Port{{"web", 80}},
   624  			},
   625  		},
   626  	}
   627  
   628  	err := r1.Add(r2)
   629  	if err != nil {
   630  		t.Fatalf("Err: %v", err)
   631  	}
   632  
   633  	expect := &Resources{
   634  		CPU:      3000,
   635  		MemoryMB: 3072,
   636  		DiskMB:   15000,
   637  		IOPS:     150,
   638  		Networks: []*NetworkResource{
   639  			&NetworkResource{
   640  				CIDR:          "10.0.0.0/8",
   641  				MBits:         150,
   642  				ReservedPorts: []Port{{"ssh", 22}, {"web", 80}},
   643  			},
   644  		},
   645  	}
   646  
   647  	if !reflect.DeepEqual(expect.Networks, r1.Networks) {
   648  		t.Fatalf("bad: %#v %#v", expect, r1)
   649  	}
   650  }
   651  
   652  func TestResource_Add_Network(t *testing.T) {
   653  	r1 := &Resources{}
   654  	r2 := &Resources{
   655  		Networks: []*NetworkResource{
   656  			&NetworkResource{
   657  				MBits:        50,
   658  				DynamicPorts: []Port{{"http", 0}, {"https", 0}},
   659  			},
   660  		},
   661  	}
   662  	r3 := &Resources{
   663  		Networks: []*NetworkResource{
   664  			&NetworkResource{
   665  				MBits:        25,
   666  				DynamicPorts: []Port{{"admin", 0}},
   667  			},
   668  		},
   669  	}
   670  
   671  	err := r1.Add(r2)
   672  	if err != nil {
   673  		t.Fatalf("Err: %v", err)
   674  	}
   675  	err = r1.Add(r3)
   676  	if err != nil {
   677  		t.Fatalf("Err: %v", err)
   678  	}
   679  
   680  	expect := &Resources{
   681  		Networks: []*NetworkResource{
   682  			&NetworkResource{
   683  				MBits:        75,
   684  				DynamicPorts: []Port{{"http", 0}, {"https", 0}, {"admin", 0}},
   685  			},
   686  		},
   687  	}
   688  
   689  	if !reflect.DeepEqual(expect.Networks, r1.Networks) {
   690  		t.Fatalf("bad: %#v %#v", expect.Networks[0], r1.Networks[0])
   691  	}
   692  }
   693  
   694  func TestEncodeDecode(t *testing.T) {
   695  	type FooRequest struct {
   696  		Foo string
   697  		Bar int
   698  		Baz bool
   699  	}
   700  	arg := &FooRequest{
   701  		Foo: "test",
   702  		Bar: 42,
   703  		Baz: true,
   704  	}
   705  	buf, err := Encode(1, arg)
   706  	if err != nil {
   707  		t.Fatalf("err: %v", err)
   708  	}
   709  
   710  	var out FooRequest
   711  	err = Decode(buf[1:], &out)
   712  	if err != nil {
   713  		t.Fatalf("err: %v", err)
   714  	}
   715  
   716  	if !reflect.DeepEqual(arg, &out) {
   717  		t.Fatalf("bad: %#v %#v", arg, out)
   718  	}
   719  }
   720  
   721  func BenchmarkEncodeDecode(b *testing.B) {
   722  	job := testJob()
   723  
   724  	for i := 0; i < b.N; i++ {
   725  		buf, err := Encode(1, job)
   726  		if err != nil {
   727  			b.Fatalf("err: %v", err)
   728  		}
   729  
   730  		var out Job
   731  		err = Decode(buf[1:], &out)
   732  		if err != nil {
   733  			b.Fatalf("err: %v", err)
   734  		}
   735  	}
   736  }
   737  
   738  func TestInvalidServiceCheck(t *testing.T) {
   739  	s := Service{
   740  		Name:      "service-name",
   741  		PortLabel: "bar",
   742  		Checks: []*ServiceCheck{
   743  			{
   744  				Name: "check-name",
   745  				Type: "lol",
   746  			},
   747  		},
   748  	}
   749  	if err := s.Validate(); err == nil {
   750  		t.Fatalf("Service should be invalid (invalid type)")
   751  	}
   752  
   753  	s = Service{
   754  		Name:      "service.name",
   755  		PortLabel: "bar",
   756  	}
   757  	if err := s.Validate(); err == nil {
   758  		t.Fatalf("Service should be invalid (contains a dot): %v", err)
   759  	}
   760  
   761  	s = Service{
   762  		Name:      "-my-service",
   763  		PortLabel: "bar",
   764  	}
   765  	if err := s.Validate(); err == nil {
   766  		t.Fatalf("Service should be invalid (begins with a hyphen): %v", err)
   767  	}
   768  
   769  	s = Service{
   770  		Name:      "abcdef0123456789-abcdef0123456789-abcdef0123456789-abcdef0123456",
   771  		PortLabel: "bar",
   772  	}
   773  	if err := s.Validate(); err == nil {
   774  		t.Fatalf("Service should be invalid (too long): %v", err)
   775  	}
   776  
   777  	s = Service{
   778  		Name: "service-name",
   779  		Checks: []*ServiceCheck{
   780  			{
   781  				Name:     "check-tcp",
   782  				Type:     ServiceCheckTCP,
   783  				Interval: 5 * time.Second,
   784  				Timeout:  2 * time.Second,
   785  			},
   786  			{
   787  				Name:     "check-http",
   788  				Type:     ServiceCheckHTTP,
   789  				Path:     "/foo",
   790  				Interval: 5 * time.Second,
   791  				Timeout:  2 * time.Second,
   792  			},
   793  		},
   794  	}
   795  	if err := s.Validate(); err == nil {
   796  		t.Fatalf("service should be invalid (tcp/http checks with no port): %v", err)
   797  	}
   798  
   799  	s = Service{
   800  		Name: "service-name",
   801  		Checks: []*ServiceCheck{
   802  			{
   803  				Name:     "check-script",
   804  				Type:     ServiceCheckScript,
   805  				Command:  "/bin/date",
   806  				Interval: 5 * time.Second,
   807  				Timeout:  2 * time.Second,
   808  			},
   809  		},
   810  	}
   811  	if err := s.Validate(); err != nil {
   812  		t.Fatalf("un-expected error: %v", err)
   813  	}
   814  }
   815  
   816  func TestDistinctCheckID(t *testing.T) {
   817  	c1 := ServiceCheck{
   818  		Name:     "web-health",
   819  		Type:     "http",
   820  		Path:     "/health",
   821  		Interval: 2 * time.Second,
   822  		Timeout:  3 * time.Second,
   823  	}
   824  	c2 := ServiceCheck{
   825  		Name:     "web-health",
   826  		Type:     "http",
   827  		Path:     "/health1",
   828  		Interval: 2 * time.Second,
   829  		Timeout:  3 * time.Second,
   830  	}
   831  
   832  	c3 := ServiceCheck{
   833  		Name:     "web-health",
   834  		Type:     "http",
   835  		Path:     "/health",
   836  		Interval: 4 * time.Second,
   837  		Timeout:  3 * time.Second,
   838  	}
   839  	serviceID := "123"
   840  	c1Hash := c1.Hash(serviceID)
   841  	c2Hash := c2.Hash(serviceID)
   842  	c3Hash := c3.Hash(serviceID)
   843  
   844  	if c1Hash == c2Hash || c1Hash == c3Hash || c3Hash == c2Hash {
   845  		t.Fatalf("Checks need to be uniq c1: %s, c2: %s, c3: %s", c1Hash, c2Hash, c3Hash)
   846  	}
   847  
   848  }
   849  
   850  func TestService_Canonicalize(t *testing.T) {
   851  	job := "example"
   852  	taskGroup := "cache"
   853  	task := "redis"
   854  
   855  	s := Service{
   856  		Name: "${TASK}-db",
   857  	}
   858  
   859  	s.Canonicalize(job, taskGroup, task)
   860  	if s.Name != "redis-db" {
   861  		t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name)
   862  	}
   863  
   864  	s.Name = "db"
   865  	s.Canonicalize(job, taskGroup, task)
   866  	if s.Name != "db" {
   867  		t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name)
   868  	}
   869  
   870  	s.Name = "${JOB}-${TASKGROUP}-${TASK}-db"
   871  	s.Canonicalize(job, taskGroup, task)
   872  	if s.Name != "example-cache-redis-db" {
   873  		t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name)
   874  	}
   875  
   876  	s.Name = "${BASE}-db"
   877  	s.Canonicalize(job, taskGroup, task)
   878  	if s.Name != "example-cache-redis-db" {
   879  		t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name)
   880  	}
   881  
   882  }
   883  
   884  func TestJob_ExpandServiceNames(t *testing.T) {
   885  	j := &Job{
   886  		Name: "my-job",
   887  		TaskGroups: []*TaskGroup{
   888  			&TaskGroup{
   889  				Name: "web",
   890  				Tasks: []*Task{
   891  					{
   892  						Name: "frontend",
   893  						Services: []*Service{
   894  							{
   895  								Name: "${BASE}-default",
   896  							},
   897  							{
   898  								Name: "jmx",
   899  							},
   900  						},
   901  					},
   902  				},
   903  			},
   904  			&TaskGroup{
   905  				Name: "admin",
   906  				Tasks: []*Task{
   907  					{
   908  						Name: "admin-web",
   909  					},
   910  				},
   911  			},
   912  		},
   913  	}
   914  
   915  	j.Canonicalize()
   916  
   917  	service1Name := j.TaskGroups[0].Tasks[0].Services[0].Name
   918  	if service1Name != "my-job-web-frontend-default" {
   919  		t.Fatalf("Expected Service Name: %s, Actual: %s", "my-job-web-frontend-default", service1Name)
   920  	}
   921  
   922  	service2Name := j.TaskGroups[0].Tasks[0].Services[1].Name
   923  	if service2Name != "jmx" {
   924  		t.Fatalf("Expected Service Name: %s, Actual: %s", "jmx", service2Name)
   925  	}
   926  
   927  }
   928  
   929  func TestPeriodicConfig_EnabledInvalid(t *testing.T) {
   930  	// Create a config that is enabled but with no interval specified.
   931  	p := &PeriodicConfig{Enabled: true}
   932  	if err := p.Validate(); err == nil {
   933  		t.Fatal("Enabled PeriodicConfig with no spec or type shouldn't be valid")
   934  	}
   935  
   936  	// Create a config that is enabled, with a spec but no type specified.
   937  	p = &PeriodicConfig{Enabled: true, Spec: "foo"}
   938  	if err := p.Validate(); err == nil {
   939  		t.Fatal("Enabled PeriodicConfig with no spec type shouldn't be valid")
   940  	}
   941  
   942  	// Create a config that is enabled, with a spec type but no spec specified.
   943  	p = &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron}
   944  	if err := p.Validate(); err == nil {
   945  		t.Fatal("Enabled PeriodicConfig with no spec shouldn't be valid")
   946  	}
   947  }
   948  
   949  func TestPeriodicConfig_InvalidCron(t *testing.T) {
   950  	specs := []string{"foo", "* *", "@foo"}
   951  	for _, spec := range specs {
   952  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
   953  		if err := p.Validate(); err == nil {
   954  			t.Fatal("Invalid cron spec")
   955  		}
   956  	}
   957  }
   958  
   959  func TestPeriodicConfig_ValidCron(t *testing.T) {
   960  	specs := []string{"0 0 29 2 *", "@hourly", "0 0-15 * * *"}
   961  	for _, spec := range specs {
   962  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
   963  		if err := p.Validate(); err != nil {
   964  			t.Fatal("Passed valid cron")
   965  		}
   966  	}
   967  }
   968  
   969  func TestPeriodicConfig_NextCron(t *testing.T) {
   970  	from := time.Date(2009, time.November, 10, 23, 22, 30, 0, time.UTC)
   971  	specs := []string{"0 0 29 2 * 1980", "*/5 * * * *"}
   972  	expected := []time.Time{time.Time{}, time.Date(2009, time.November, 10, 23, 25, 0, 0, time.UTC)}
   973  	for i, spec := range specs {
   974  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
   975  		n := p.Next(from)
   976  		if expected[i] != n {
   977  			t.Fatalf("Next(%v) returned %v; want %v", from, n, expected[i])
   978  		}
   979  	}
   980  }
   981  
   982  func TestRestartPolicy_Validate(t *testing.T) {
   983  	// Policy with acceptable restart options passes
   984  	p := &RestartPolicy{
   985  		Mode:     RestartPolicyModeFail,
   986  		Attempts: 0,
   987  	}
   988  	if err := p.Validate(); err != nil {
   989  		t.Fatalf("err: %v", err)
   990  	}
   991  
   992  	// Policy with ambiguous restart options fails
   993  	p = &RestartPolicy{
   994  		Mode:     RestartPolicyModeDelay,
   995  		Attempts: 0,
   996  	}
   997  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "ambiguous") {
   998  		t.Fatalf("expect ambiguity error, got: %v", err)
   999  	}
  1000  
  1001  	// Bad policy mode fails
  1002  	p = &RestartPolicy{
  1003  		Mode:     "nope",
  1004  		Attempts: 1,
  1005  	}
  1006  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "mode") {
  1007  		t.Fatalf("expect mode error, got: %v", err)
  1008  	}
  1009  
  1010  	// Fails when attempts*delay does not fit inside interval
  1011  	p = &RestartPolicy{
  1012  		Mode:     RestartPolicyModeDelay,
  1013  		Attempts: 3,
  1014  		Delay:    5 * time.Second,
  1015  		Interval: time.Second,
  1016  	}
  1017  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "can't restart") {
  1018  		t.Fatalf("expect restart interval error, got: %v", err)
  1019  	}
  1020  }
  1021  
  1022  func TestAllocation_Index(t *testing.T) {
  1023  	a1 := Allocation{Name: "example.cache[0]"}
  1024  	e1 := 0
  1025  	a2 := Allocation{Name: "ex[123]am123ple.c311ac[123]he12[1][77]"}
  1026  	e2 := 77
  1027  
  1028  	if a1.Index() != e1 || a2.Index() != e2 {
  1029  		t.Fatal()
  1030  	}
  1031  }
  1032  
  1033  func TestTaskArtifact_Validate_Source(t *testing.T) {
  1034  	valid := &TaskArtifact{GetterSource: "google.com"}
  1035  	if err := valid.Validate(); err != nil {
  1036  		t.Fatalf("unexpected error: %v", err)
  1037  	}
  1038  }
  1039  
  1040  func TestTaskArtifact_Validate_Dest(t *testing.T) {
  1041  	valid := &TaskArtifact{GetterSource: "google.com"}
  1042  	if err := valid.Validate(); err != nil {
  1043  		t.Fatalf("unexpected error: %v", err)
  1044  	}
  1045  
  1046  	valid.RelativeDest = "local/"
  1047  	if err := valid.Validate(); err != nil {
  1048  		t.Fatalf("unexpected error: %v", err)
  1049  	}
  1050  
  1051  	valid.RelativeDest = "local/.."
  1052  	if err := valid.Validate(); err != nil {
  1053  		t.Fatalf("unexpected error: %v", err)
  1054  	}
  1055  
  1056  	valid.RelativeDest = "local/../.."
  1057  	if err := valid.Validate(); err == nil {
  1058  		t.Fatalf("expected error: %v", err)
  1059  	}
  1060  }
  1061  
  1062  func TestTaskArtifact_Validate_Checksum(t *testing.T) {
  1063  	cases := []struct {
  1064  		Input *TaskArtifact
  1065  		Err   bool
  1066  	}{
  1067  		{
  1068  			&TaskArtifact{
  1069  				GetterSource: "foo.com",
  1070  				GetterOptions: map[string]string{
  1071  					"checksum": "no-type",
  1072  				},
  1073  			},
  1074  			true,
  1075  		},
  1076  		{
  1077  			&TaskArtifact{
  1078  				GetterSource: "foo.com",
  1079  				GetterOptions: map[string]string{
  1080  					"checksum": "md5:toosmall",
  1081  				},
  1082  			},
  1083  			true,
  1084  		},
  1085  		{
  1086  			&TaskArtifact{
  1087  				GetterSource: "foo.com",
  1088  				GetterOptions: map[string]string{
  1089  					"checksum": "invalid:type",
  1090  				},
  1091  			},
  1092  			true,
  1093  		},
  1094  	}
  1095  
  1096  	for i, tc := range cases {
  1097  		err := tc.Input.Validate()
  1098  		if (err != nil) != tc.Err {
  1099  			t.Fatalf("case %d: %v", i, err)
  1100  			continue
  1101  		}
  1102  	}
  1103  }
  1104  
  1105  func TestAllocation_Terminated(t *testing.T) {
  1106  	type desiredState struct {
  1107  		ClientStatus  string
  1108  		DesiredStatus string
  1109  		Terminated    bool
  1110  	}
  1111  
  1112  	harness := []desiredState{
  1113  		{
  1114  			ClientStatus:  AllocClientStatusPending,
  1115  			DesiredStatus: AllocDesiredStatusStop,
  1116  			Terminated:    false,
  1117  		},
  1118  		{
  1119  			ClientStatus:  AllocClientStatusRunning,
  1120  			DesiredStatus: AllocDesiredStatusStop,
  1121  			Terminated:    false,
  1122  		},
  1123  		{
  1124  			ClientStatus:  AllocClientStatusFailed,
  1125  			DesiredStatus: AllocDesiredStatusStop,
  1126  			Terminated:    true,
  1127  		},
  1128  		{
  1129  			ClientStatus:  AllocClientStatusFailed,
  1130  			DesiredStatus: AllocDesiredStatusRun,
  1131  			Terminated:    true,
  1132  		},
  1133  	}
  1134  
  1135  	for _, state := range harness {
  1136  		alloc := Allocation{}
  1137  		alloc.DesiredStatus = state.DesiredStatus
  1138  		alloc.ClientStatus = state.ClientStatus
  1139  		if alloc.Terminated() != state.Terminated {
  1140  			t.Fatalf("expected: %v, actual: %v", state.Terminated, alloc.Terminated())
  1141  		}
  1142  	}
  1143  }