github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/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/go-multierror"
    10  )
    11  
    12  func TestJob_Validate(t *testing.T) {
    13  	j := &Job{}
    14  	err := j.Validate()
    15  	mErr := err.(*multierror.Error)
    16  	if !strings.Contains(mErr.Errors[0].Error(), "job region") {
    17  		t.Fatalf("err: %s", err)
    18  	}
    19  	if !strings.Contains(mErr.Errors[1].Error(), "job ID") {
    20  		t.Fatalf("err: %s", err)
    21  	}
    22  	if !strings.Contains(mErr.Errors[2].Error(), "job name") {
    23  		t.Fatalf("err: %s", err)
    24  	}
    25  	if !strings.Contains(mErr.Errors[3].Error(), "job type") {
    26  		t.Fatalf("err: %s", err)
    27  	}
    28  	if !strings.Contains(mErr.Errors[4].Error(), "priority") {
    29  		t.Fatalf("err: %s", err)
    30  	}
    31  	if !strings.Contains(mErr.Errors[5].Error(), "datacenters") {
    32  		t.Fatalf("err: %s", err)
    33  	}
    34  	if !strings.Contains(mErr.Errors[6].Error(), "task groups") {
    35  		t.Fatalf("err: %s", err)
    36  	}
    37  
    38  	j = &Job{
    39  		Type: JobTypeService,
    40  		Periodic: &PeriodicConfig{
    41  			Enabled: true,
    42  		},
    43  	}
    44  	err = j.Validate()
    45  	mErr = err.(*multierror.Error)
    46  	if !strings.Contains(mErr.Error(), "Periodic") {
    47  		t.Fatalf("err: %s", err)
    48  	}
    49  
    50  	j = &Job{
    51  		Region:      "global",
    52  		ID:          GenerateUUID(),
    53  		Name:        "my-job",
    54  		Type:        JobTypeService,
    55  		Priority:    50,
    56  		Datacenters: []string{"dc1"},
    57  		TaskGroups: []*TaskGroup{
    58  			&TaskGroup{
    59  				Name: "web",
    60  				RestartPolicy: &RestartPolicy{
    61  					Interval: 5 * time.Minute,
    62  					Delay:    10 * time.Second,
    63  					Attempts: 10,
    64  				},
    65  			},
    66  			&TaskGroup{
    67  				Name: "web",
    68  				RestartPolicy: &RestartPolicy{
    69  					Interval: 5 * time.Minute,
    70  					Delay:    10 * time.Second,
    71  					Attempts: 10,
    72  				},
    73  			},
    74  			&TaskGroup{
    75  				RestartPolicy: &RestartPolicy{
    76  					Interval: 5 * time.Minute,
    77  					Delay:    10 * time.Second,
    78  					Attempts: 10,
    79  				},
    80  			},
    81  		},
    82  	}
    83  	err = j.Validate()
    84  	mErr = err.(*multierror.Error)
    85  	if !strings.Contains(mErr.Errors[0].Error(), "2 redefines 'web' from group 1") {
    86  		t.Fatalf("err: %s", err)
    87  	}
    88  	if !strings.Contains(mErr.Errors[1].Error(), "group 3 missing name") {
    89  		t.Fatalf("err: %s", err)
    90  	}
    91  	if !strings.Contains(mErr.Errors[2].Error(), "Task group 1 validation failed") {
    92  		t.Fatalf("err: %s", err)
    93  	}
    94  }
    95  
    96  func testJob() *Job {
    97  	return &Job{
    98  		Region:      "global",
    99  		ID:          GenerateUUID(),
   100  		Name:        "my-job",
   101  		Type:        JobTypeService,
   102  		Priority:    50,
   103  		AllAtOnce:   false,
   104  		Datacenters: []string{"dc1"},
   105  		Constraints: []*Constraint{
   106  			&Constraint{
   107  				LTarget: "$attr.kernel.name",
   108  				RTarget: "linux",
   109  				Operand: "=",
   110  			},
   111  		},
   112  		Periodic: &PeriodicConfig{
   113  			Enabled: false,
   114  		},
   115  		TaskGroups: []*TaskGroup{
   116  			&TaskGroup{
   117  				Name:  "web",
   118  				Count: 10,
   119  				RestartPolicy: &RestartPolicy{
   120  					Attempts: 3,
   121  					Interval: 10 * time.Minute,
   122  					Delay:    1 * time.Minute,
   123  				},
   124  				Tasks: []*Task{
   125  					&Task{
   126  						Name:   "web",
   127  						Driver: "exec",
   128  						Config: map[string]interface{}{
   129  							"command": "/bin/date",
   130  						},
   131  						Env: map[string]string{
   132  							"FOO": "bar",
   133  						},
   134  						Artifacts: []*TaskArtifact{
   135  							{
   136  								GetterSource: "http://foo.com",
   137  							},
   138  						},
   139  						Services: []*Service{
   140  							{
   141  								Name:      "${TASK}-frontend",
   142  								PortLabel: "http",
   143  							},
   144  						},
   145  						Resources: &Resources{
   146  							CPU:      500,
   147  							MemoryMB: 256,
   148  							Networks: []*NetworkResource{
   149  								&NetworkResource{
   150  									MBits:        50,
   151  									DynamicPorts: []Port{{Label: "http"}},
   152  								},
   153  							},
   154  						},
   155  					},
   156  				},
   157  				Meta: map[string]string{
   158  					"elb_check_type":     "http",
   159  					"elb_check_interval": "30s",
   160  					"elb_check_min":      "3",
   161  				},
   162  			},
   163  		},
   164  		Meta: map[string]string{
   165  			"owner": "armon",
   166  		},
   167  	}
   168  }
   169  
   170  func TestJob_Copy(t *testing.T) {
   171  	j := testJob()
   172  	c := j.Copy()
   173  	if !reflect.DeepEqual(j, c) {
   174  		t.Fatalf("Copy() returned an unequal Job; got %#v; want %#v", c, j)
   175  	}
   176  }
   177  
   178  func TestJob_IsPeriodic(t *testing.T) {
   179  	j := &Job{
   180  		Type: JobTypeService,
   181  		Periodic: &PeriodicConfig{
   182  			Enabled: true,
   183  		},
   184  	}
   185  	if !j.IsPeriodic() {
   186  		t.Fatalf("IsPeriodic() returned false on periodic job")
   187  	}
   188  
   189  	j = &Job{
   190  		Type: JobTypeService,
   191  	}
   192  	if j.IsPeriodic() {
   193  		t.Fatalf("IsPeriodic() returned true on non-periodic job")
   194  	}
   195  }
   196  
   197  func TestTaskGroup_Validate(t *testing.T) {
   198  	tg := &TaskGroup{
   199  		Count: -1,
   200  		RestartPolicy: &RestartPolicy{
   201  			Interval: 5 * time.Minute,
   202  			Delay:    10 * time.Second,
   203  			Attempts: 10,
   204  			Mode:     RestartPolicyModeDelay,
   205  		},
   206  	}
   207  	err := tg.Validate()
   208  	mErr := err.(*multierror.Error)
   209  	if !strings.Contains(mErr.Errors[0].Error(), "group name") {
   210  		t.Fatalf("err: %s", err)
   211  	}
   212  	if !strings.Contains(mErr.Errors[1].Error(), "count can't be negative") {
   213  		t.Fatalf("err: %s", err)
   214  	}
   215  	if !strings.Contains(mErr.Errors[2].Error(), "Missing tasks") {
   216  		t.Fatalf("err: %s", err)
   217  	}
   218  
   219  	tg = &TaskGroup{
   220  		Name:  "web",
   221  		Count: 1,
   222  		Tasks: []*Task{
   223  			&Task{Name: "web"},
   224  			&Task{Name: "web"},
   225  			&Task{},
   226  		},
   227  		RestartPolicy: &RestartPolicy{
   228  			Interval: 5 * time.Minute,
   229  			Delay:    10 * time.Second,
   230  			Attempts: 10,
   231  			Mode:     RestartPolicyModeDelay,
   232  		},
   233  	}
   234  	err = tg.Validate()
   235  	mErr = err.(*multierror.Error)
   236  	if !strings.Contains(mErr.Errors[0].Error(), "2 redefines 'web' from task 1") {
   237  		t.Fatalf("err: %s", err)
   238  	}
   239  	if !strings.Contains(mErr.Errors[1].Error(), "Task 3 missing name") {
   240  		t.Fatalf("err: %s", err)
   241  	}
   242  	if !strings.Contains(mErr.Errors[2].Error(), "Task 1 validation failed") {
   243  		t.Fatalf("err: %s", err)
   244  	}
   245  }
   246  
   247  func TestTask_Validate(t *testing.T) {
   248  	task := &Task{}
   249  	err := task.Validate()
   250  	mErr := err.(*multierror.Error)
   251  	if !strings.Contains(mErr.Errors[0].Error(), "task name") {
   252  		t.Fatalf("err: %s", err)
   253  	}
   254  	if !strings.Contains(mErr.Errors[1].Error(), "task driver") {
   255  		t.Fatalf("err: %s", err)
   256  	}
   257  	if !strings.Contains(mErr.Errors[2].Error(), "task resources") {
   258  		t.Fatalf("err: %s", err)
   259  	}
   260  
   261  	task = &Task{
   262  		Name:   "web",
   263  		Driver: "docker",
   264  		Resources: &Resources{
   265  			CPU:      100,
   266  			DiskMB:   200,
   267  			MemoryMB: 100,
   268  			IOPS:     10,
   269  		},
   270  		LogConfig: DefaultLogConfig(),
   271  	}
   272  	err = task.Validate()
   273  	if err != nil {
   274  		t.Fatalf("err: %s", err)
   275  	}
   276  }
   277  
   278  func TestTask_Validate_Services(t *testing.T) {
   279  	s1 := &Service{
   280  		Name:      "service-name",
   281  		PortLabel: "bar",
   282  		Checks: []*ServiceCheck{
   283  			{
   284  				Name: "check-name",
   285  				Type: ServiceCheckTCP,
   286  			},
   287  			{
   288  				Name: "check-name",
   289  				Type: ServiceCheckTCP,
   290  			},
   291  		},
   292  	}
   293  
   294  	s2 := &Service{
   295  		Name: "service-name",
   296  	}
   297  
   298  	task := &Task{
   299  		Name:   "web",
   300  		Driver: "docker",
   301  		Resources: &Resources{
   302  			CPU:      100,
   303  			DiskMB:   200,
   304  			MemoryMB: 100,
   305  			IOPS:     10,
   306  		},
   307  		Services: []*Service{s1, s2},
   308  	}
   309  	err := task.Validate()
   310  	if err == nil {
   311  		t.Fatal("expected an error")
   312  	}
   313  	if !strings.Contains(err.Error(), "referenced by services service-name does not exist") {
   314  		t.Fatalf("err: %s", err)
   315  	}
   316  
   317  	if !strings.Contains(err.Error(), "service \"service-name\" is duplicate") {
   318  		t.Fatalf("err: %v", err)
   319  	}
   320  
   321  	if !strings.Contains(err.Error(), "check \"check-name\" is duplicate") {
   322  		t.Fatalf("err: %v", err)
   323  	}
   324  }
   325  
   326  func TestTask_Validate_LogConfig(t *testing.T) {
   327  	task := &Task{
   328  		LogConfig: DefaultLogConfig(),
   329  		Resources: &Resources{
   330  			DiskMB: 1,
   331  		},
   332  	}
   333  
   334  	err := task.Validate()
   335  	mErr := err.(*multierror.Error)
   336  	if !strings.Contains(mErr.Errors[3].Error(), "log storage") {
   337  		t.Fatalf("err: %s", err)
   338  	}
   339  }
   340  
   341  func TestConstraint_Validate(t *testing.T) {
   342  	c := &Constraint{}
   343  	err := c.Validate()
   344  	mErr := err.(*multierror.Error)
   345  	if !strings.Contains(mErr.Errors[0].Error(), "Missing constraint operand") {
   346  		t.Fatalf("err: %s", err)
   347  	}
   348  
   349  	c = &Constraint{
   350  		LTarget: "$attr.kernel.name",
   351  		RTarget: "linux",
   352  		Operand: "=",
   353  	}
   354  	err = c.Validate()
   355  	if err != nil {
   356  		t.Fatalf("err: %v", err)
   357  	}
   358  
   359  	// Perform additional regexp validation
   360  	c.Operand = ConstraintRegex
   361  	c.RTarget = "(foo"
   362  	err = c.Validate()
   363  	mErr = err.(*multierror.Error)
   364  	if !strings.Contains(mErr.Errors[0].Error(), "missing closing") {
   365  		t.Fatalf("err: %s", err)
   366  	}
   367  
   368  	// Perform version validation
   369  	c.Operand = ConstraintVersion
   370  	c.RTarget = "~> foo"
   371  	err = c.Validate()
   372  	mErr = err.(*multierror.Error)
   373  	if !strings.Contains(mErr.Errors[0].Error(), "Malformed constraint") {
   374  		t.Fatalf("err: %s", err)
   375  	}
   376  }
   377  
   378  func TestResource_NetIndex(t *testing.T) {
   379  	r := &Resources{
   380  		Networks: []*NetworkResource{
   381  			&NetworkResource{Device: "eth0"},
   382  			&NetworkResource{Device: "lo0"},
   383  			&NetworkResource{Device: ""},
   384  		},
   385  	}
   386  	if idx := r.NetIndex(&NetworkResource{Device: "eth0"}); idx != 0 {
   387  		t.Fatalf("Bad: %d", idx)
   388  	}
   389  	if idx := r.NetIndex(&NetworkResource{Device: "lo0"}); idx != 1 {
   390  		t.Fatalf("Bad: %d", idx)
   391  	}
   392  	if idx := r.NetIndex(&NetworkResource{Device: "eth1"}); idx != -1 {
   393  		t.Fatalf("Bad: %d", idx)
   394  	}
   395  }
   396  
   397  func TestResource_Superset(t *testing.T) {
   398  	r1 := &Resources{
   399  		CPU:      2000,
   400  		MemoryMB: 2048,
   401  		DiskMB:   10000,
   402  		IOPS:     100,
   403  	}
   404  	r2 := &Resources{
   405  		CPU:      2000,
   406  		MemoryMB: 1024,
   407  		DiskMB:   5000,
   408  		IOPS:     50,
   409  	}
   410  
   411  	if s, _ := r1.Superset(r1); !s {
   412  		t.Fatalf("bad")
   413  	}
   414  	if s, _ := r1.Superset(r2); !s {
   415  		t.Fatalf("bad")
   416  	}
   417  	if s, _ := r2.Superset(r1); s {
   418  		t.Fatalf("bad")
   419  	}
   420  	if s, _ := r2.Superset(r2); !s {
   421  		t.Fatalf("bad")
   422  	}
   423  }
   424  
   425  func TestResource_Add(t *testing.T) {
   426  	r1 := &Resources{
   427  		CPU:      2000,
   428  		MemoryMB: 2048,
   429  		DiskMB:   10000,
   430  		IOPS:     100,
   431  		Networks: []*NetworkResource{
   432  			&NetworkResource{
   433  				CIDR:          "10.0.0.0/8",
   434  				MBits:         100,
   435  				ReservedPorts: []Port{{"ssh", 22}},
   436  			},
   437  		},
   438  	}
   439  	r2 := &Resources{
   440  		CPU:      2000,
   441  		MemoryMB: 1024,
   442  		DiskMB:   5000,
   443  		IOPS:     50,
   444  		Networks: []*NetworkResource{
   445  			&NetworkResource{
   446  				IP:            "10.0.0.1",
   447  				MBits:         50,
   448  				ReservedPorts: []Port{{"web", 80}},
   449  			},
   450  		},
   451  	}
   452  
   453  	err := r1.Add(r2)
   454  	if err != nil {
   455  		t.Fatalf("Err: %v", err)
   456  	}
   457  
   458  	expect := &Resources{
   459  		CPU:      3000,
   460  		MemoryMB: 3072,
   461  		DiskMB:   15000,
   462  		IOPS:     150,
   463  		Networks: []*NetworkResource{
   464  			&NetworkResource{
   465  				CIDR:          "10.0.0.0/8",
   466  				MBits:         150,
   467  				ReservedPorts: []Port{{"ssh", 22}, {"web", 80}},
   468  			},
   469  		},
   470  	}
   471  
   472  	if !reflect.DeepEqual(expect.Networks, r1.Networks) {
   473  		t.Fatalf("bad: %#v %#v", expect, r1)
   474  	}
   475  }
   476  
   477  func TestResource_Add_Network(t *testing.T) {
   478  	r1 := &Resources{}
   479  	r2 := &Resources{
   480  		Networks: []*NetworkResource{
   481  			&NetworkResource{
   482  				MBits:        50,
   483  				DynamicPorts: []Port{{"http", 0}, {"https", 0}},
   484  			},
   485  		},
   486  	}
   487  	r3 := &Resources{
   488  		Networks: []*NetworkResource{
   489  			&NetworkResource{
   490  				MBits:        25,
   491  				DynamicPorts: []Port{{"admin", 0}},
   492  			},
   493  		},
   494  	}
   495  
   496  	err := r1.Add(r2)
   497  	if err != nil {
   498  		t.Fatalf("Err: %v", err)
   499  	}
   500  	err = r1.Add(r3)
   501  	if err != nil {
   502  		t.Fatalf("Err: %v", err)
   503  	}
   504  
   505  	expect := &Resources{
   506  		Networks: []*NetworkResource{
   507  			&NetworkResource{
   508  				MBits:        75,
   509  				DynamicPorts: []Port{{"http", 0}, {"https", 0}, {"admin", 0}},
   510  			},
   511  		},
   512  	}
   513  
   514  	if !reflect.DeepEqual(expect.Networks, r1.Networks) {
   515  		t.Fatalf("bad: %#v %#v", expect.Networks[0], r1.Networks[0])
   516  	}
   517  }
   518  
   519  func TestEncodeDecode(t *testing.T) {
   520  	type FooRequest struct {
   521  		Foo string
   522  		Bar int
   523  		Baz bool
   524  	}
   525  	arg := &FooRequest{
   526  		Foo: "test",
   527  		Bar: 42,
   528  		Baz: true,
   529  	}
   530  	buf, err := Encode(1, arg)
   531  	if err != nil {
   532  		t.Fatalf("err: %v", err)
   533  	}
   534  
   535  	var out FooRequest
   536  	err = Decode(buf[1:], &out)
   537  	if err != nil {
   538  		t.Fatalf("err: %v", err)
   539  	}
   540  
   541  	if !reflect.DeepEqual(arg, &out) {
   542  		t.Fatalf("bad: %#v %#v", arg, out)
   543  	}
   544  }
   545  
   546  func BenchmarkEncodeDecode(b *testing.B) {
   547  	job := testJob()
   548  
   549  	for i := 0; i < b.N; i++ {
   550  		buf, err := Encode(1, job)
   551  		if err != nil {
   552  			b.Fatalf("err: %v", err)
   553  		}
   554  
   555  		var out Job
   556  		err = Decode(buf[1:], &out)
   557  		if err != nil {
   558  			b.Fatalf("err: %v", err)
   559  		}
   560  	}
   561  }
   562  
   563  func TestInvalidServiceCheck(t *testing.T) {
   564  	s := Service{
   565  		Name:      "service-name",
   566  		PortLabel: "bar",
   567  		Checks: []*ServiceCheck{
   568  			{
   569  				Name: "check-name",
   570  				Type: "lol",
   571  			},
   572  		},
   573  	}
   574  	if err := s.Validate(); err == nil {
   575  		t.Fatalf("Service should be invalid (invalid type)")
   576  	}
   577  
   578  	s = Service{
   579  		Name:      "service.name",
   580  		PortLabel: "bar",
   581  	}
   582  	if err := s.Validate(); err == nil {
   583  		t.Fatalf("Service should be invalid (contains a dot): %v", err)
   584  	}
   585  
   586  	s = Service{
   587  		Name:      "-my-service",
   588  		PortLabel: "bar",
   589  	}
   590  	if err := s.Validate(); err == nil {
   591  		t.Fatalf("Service should be invalid (begins with a hyphen): %v", err)
   592  	}
   593  
   594  	s = Service{
   595  		Name:      "abcdef0123456789-abcdef0123456789-abcdef0123456789-abcdef0123456",
   596  		PortLabel: "bar",
   597  	}
   598  	if err := s.Validate(); err == nil {
   599  		t.Fatalf("Service should be invalid (too long): %v", err)
   600  	}
   601  
   602  	s = Service{
   603  		Name: "service-name",
   604  		Checks: []*ServiceCheck{
   605  			{
   606  				Name:     "check-tcp",
   607  				Type:     ServiceCheckTCP,
   608  				Interval: 5 * time.Second,
   609  				Timeout:  2 * time.Second,
   610  			},
   611  			{
   612  				Name:     "check-http",
   613  				Type:     ServiceCheckHTTP,
   614  				Path:     "/foo",
   615  				Interval: 5 * time.Second,
   616  				Timeout:  2 * time.Second,
   617  			},
   618  		},
   619  	}
   620  	if err := s.Validate(); err == nil {
   621  		t.Fatalf("service should be invalid (tcp/http checks with no port): %v", err)
   622  	}
   623  
   624  	s = Service{
   625  		Name: "service-name",
   626  		Checks: []*ServiceCheck{
   627  			{
   628  				Name:     "check-script",
   629  				Type:     ServiceCheckScript,
   630  				Command:  "/bin/date",
   631  				Interval: 5 * time.Second,
   632  				Timeout:  2 * time.Second,
   633  			},
   634  		},
   635  	}
   636  	if err := s.Validate(); err != nil {
   637  		t.Fatalf("un-expected error: %v", err)
   638  	}
   639  }
   640  
   641  func TestDistinctCheckID(t *testing.T) {
   642  	c1 := ServiceCheck{
   643  		Name:     "web-health",
   644  		Type:     "http",
   645  		Path:     "/health",
   646  		Interval: 2 * time.Second,
   647  		Timeout:  3 * time.Second,
   648  	}
   649  	c2 := ServiceCheck{
   650  		Name:     "web-health",
   651  		Type:     "http",
   652  		Path:     "/health1",
   653  		Interval: 2 * time.Second,
   654  		Timeout:  3 * time.Second,
   655  	}
   656  
   657  	c3 := ServiceCheck{
   658  		Name:     "web-health",
   659  		Type:     "http",
   660  		Path:     "/health",
   661  		Interval: 4 * time.Second,
   662  		Timeout:  3 * time.Second,
   663  	}
   664  	serviceID := "123"
   665  	c1Hash := c1.Hash(serviceID)
   666  	c2Hash := c2.Hash(serviceID)
   667  	c3Hash := c3.Hash(serviceID)
   668  
   669  	if c1Hash == c2Hash || c1Hash == c3Hash || c3Hash == c2Hash {
   670  		t.Fatalf("Checks need to be uniq c1: %s, c2: %s, c3: %s", c1Hash, c2Hash, c3Hash)
   671  	}
   672  
   673  }
   674  
   675  func TestService_InitFields(t *testing.T) {
   676  	job := "example"
   677  	taskGroup := "cache"
   678  	task := "redis"
   679  
   680  	s := Service{
   681  		Name: "${TASK}-db",
   682  	}
   683  
   684  	s.InitFields(job, taskGroup, task)
   685  	if s.Name != "redis-db" {
   686  		t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name)
   687  	}
   688  
   689  	s.Name = "db"
   690  	s.InitFields(job, taskGroup, task)
   691  	if s.Name != "db" {
   692  		t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name)
   693  	}
   694  
   695  	s.Name = "${JOB}-${TASKGROUP}-${TASK}-db"
   696  	s.InitFields(job, taskGroup, task)
   697  	if s.Name != "example-cache-redis-db" {
   698  		t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name)
   699  	}
   700  
   701  	s.Name = "${BASE}-db"
   702  	s.InitFields(job, taskGroup, task)
   703  	if s.Name != "example-cache-redis-db" {
   704  		t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name)
   705  	}
   706  
   707  }
   708  
   709  func TestJob_ExpandServiceNames(t *testing.T) {
   710  	j := &Job{
   711  		Name: "my-job",
   712  		TaskGroups: []*TaskGroup{
   713  			&TaskGroup{
   714  				Name: "web",
   715  				Tasks: []*Task{
   716  					{
   717  						Name: "frontend",
   718  						Services: []*Service{
   719  							{
   720  								Name: "${BASE}-default",
   721  							},
   722  							{
   723  								Name: "jmx",
   724  							},
   725  						},
   726  					},
   727  				},
   728  			},
   729  			&TaskGroup{
   730  				Name: "admin",
   731  				Tasks: []*Task{
   732  					{
   733  						Name: "admin-web",
   734  					},
   735  				},
   736  			},
   737  		},
   738  	}
   739  
   740  	j.InitFields()
   741  
   742  	service1Name := j.TaskGroups[0].Tasks[0].Services[0].Name
   743  	if service1Name != "my-job-web-frontend-default" {
   744  		t.Fatalf("Expected Service Name: %s, Actual: %s", "my-job-web-frontend-default", service1Name)
   745  	}
   746  
   747  	service2Name := j.TaskGroups[0].Tasks[0].Services[1].Name
   748  	if service2Name != "jmx" {
   749  		t.Fatalf("Expected Service Name: %s, Actual: %s", "jmx", service2Name)
   750  	}
   751  
   752  }
   753  
   754  func TestPeriodicConfig_EnabledInvalid(t *testing.T) {
   755  	// Create a config that is enabled but with no interval specified.
   756  	p := &PeriodicConfig{Enabled: true}
   757  	if err := p.Validate(); err == nil {
   758  		t.Fatal("Enabled PeriodicConfig with no spec or type shouldn't be valid")
   759  	}
   760  
   761  	// Create a config that is enabled, with a spec but no type specified.
   762  	p = &PeriodicConfig{Enabled: true, Spec: "foo"}
   763  	if err := p.Validate(); err == nil {
   764  		t.Fatal("Enabled PeriodicConfig with no spec type shouldn't be valid")
   765  	}
   766  
   767  	// Create a config that is enabled, with a spec type but no spec specified.
   768  	p = &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron}
   769  	if err := p.Validate(); err == nil {
   770  		t.Fatal("Enabled PeriodicConfig with no spec shouldn't be valid")
   771  	}
   772  }
   773  
   774  func TestPeriodicConfig_InvalidCron(t *testing.T) {
   775  	specs := []string{"foo", "* *", "@foo"}
   776  	for _, spec := range specs {
   777  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
   778  		if err := p.Validate(); err == nil {
   779  			t.Fatal("Invalid cron spec")
   780  		}
   781  	}
   782  }
   783  
   784  func TestPeriodicConfig_ValidCron(t *testing.T) {
   785  	specs := []string{"0 0 29 2 *", "@hourly", "0 0-15 * * *"}
   786  	for _, spec := range specs {
   787  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
   788  		if err := p.Validate(); err != nil {
   789  			t.Fatal("Passed valid cron")
   790  		}
   791  	}
   792  }
   793  
   794  func TestPeriodicConfig_NextCron(t *testing.T) {
   795  	from := time.Date(2009, time.November, 10, 23, 22, 30, 0, time.UTC)
   796  	specs := []string{"0 0 29 2 * 1980", "*/5 * * * *"}
   797  	expected := []time.Time{time.Time{}, time.Date(2009, time.November, 10, 23, 25, 0, 0, time.UTC)}
   798  	for i, spec := range specs {
   799  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
   800  		n := p.Next(from)
   801  		if expected[i] != n {
   802  			t.Fatalf("Next(%v) returned %v; want %v", from, n, expected[i])
   803  		}
   804  	}
   805  }
   806  
   807  func TestRestartPolicy_Validate(t *testing.T) {
   808  	// Policy with acceptable restart options passes
   809  	p := &RestartPolicy{
   810  		Mode:     RestartPolicyModeFail,
   811  		Attempts: 0,
   812  	}
   813  	if err := p.Validate(); err != nil {
   814  		t.Fatalf("err: %v", err)
   815  	}
   816  
   817  	// Policy with ambiguous restart options fails
   818  	p = &RestartPolicy{
   819  		Mode:     RestartPolicyModeDelay,
   820  		Attempts: 0,
   821  	}
   822  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "ambiguous") {
   823  		t.Fatalf("expect ambiguity error, got: %v", err)
   824  	}
   825  
   826  	// Bad policy mode fails
   827  	p = &RestartPolicy{
   828  		Mode:     "nope",
   829  		Attempts: 1,
   830  	}
   831  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "mode") {
   832  		t.Fatalf("expect mode error, got: %v", err)
   833  	}
   834  
   835  	// Fails when attempts*delay does not fit inside interval
   836  	p = &RestartPolicy{
   837  		Mode:     RestartPolicyModeDelay,
   838  		Attempts: 3,
   839  		Delay:    5 * time.Second,
   840  		Interval: time.Second,
   841  	}
   842  	if err := p.Validate(); err == nil || !strings.Contains(err.Error(), "can't restart") {
   843  		t.Fatalf("expect restart interval error, got: %v", err)
   844  	}
   845  }
   846  
   847  func TestAllocation_Index(t *testing.T) {
   848  	a1 := Allocation{Name: "example.cache[0]"}
   849  	e1 := 0
   850  	a2 := Allocation{Name: "ex[123]am123ple.c311ac[123]he12[1][77]"}
   851  	e2 := 77
   852  
   853  	if a1.Index() != e1 || a2.Index() != e2 {
   854  		t.Fatal()
   855  	}
   856  }
   857  
   858  func TestTaskArtifact_Validate_Source(t *testing.T) {
   859  	valid := &TaskArtifact{GetterSource: "google.com"}
   860  	if err := valid.Validate(); err != nil {
   861  		t.Fatalf("unexpected error: %v", err)
   862  	}
   863  }
   864  
   865  func TestTaskArtifact_Validate_Dest(t *testing.T) {
   866  	valid := &TaskArtifact{GetterSource: "google.com"}
   867  	if err := valid.Validate(); err != nil {
   868  		t.Fatalf("unexpected error: %v", err)
   869  	}
   870  
   871  	valid.RelativeDest = "local/"
   872  	if err := valid.Validate(); err != nil {
   873  		t.Fatalf("unexpected error: %v", err)
   874  	}
   875  
   876  	valid.RelativeDest = "local/.."
   877  	if err := valid.Validate(); err != nil {
   878  		t.Fatalf("unexpected error: %v", err)
   879  	}
   880  
   881  	valid.RelativeDest = "local/../.."
   882  	if err := valid.Validate(); err == nil {
   883  		t.Fatalf("expected error: %v", err)
   884  	}
   885  }
   886  
   887  func TestTaskArtifact_Validate_Checksum(t *testing.T) {
   888  	cases := []struct {
   889  		Input *TaskArtifact
   890  		Err   bool
   891  	}{
   892  		{
   893  			&TaskArtifact{
   894  				GetterSource: "foo.com",
   895  				GetterOptions: map[string]string{
   896  					"checksum": "no-type",
   897  				},
   898  			},
   899  			true,
   900  		},
   901  		{
   902  			&TaskArtifact{
   903  				GetterSource: "foo.com",
   904  				GetterOptions: map[string]string{
   905  					"checksum": "md5:toosmall",
   906  				},
   907  			},
   908  			true,
   909  		},
   910  		{
   911  			&TaskArtifact{
   912  				GetterSource: "foo.com",
   913  				GetterOptions: map[string]string{
   914  					"checksum": "invalid:type",
   915  				},
   916  			},
   917  			true,
   918  		},
   919  	}
   920  
   921  	for i, tc := range cases {
   922  		err := tc.Input.Validate()
   923  		if (err != nil) != tc.Err {
   924  			t.Fatalf("case %d: %v", i, err)
   925  			continue
   926  		}
   927  	}
   928  }