github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/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_Copy(t *testing.T) {
    97  	j := &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  						Services: []*Service{
   135  							{
   136  								Name:      "${TASK}-frontend",
   137  								PortLabel: "http",
   138  							},
   139  						},
   140  						Resources: &Resources{
   141  							CPU:      500,
   142  							MemoryMB: 256,
   143  							Networks: []*NetworkResource{
   144  								&NetworkResource{
   145  									MBits:        50,
   146  									DynamicPorts: []Port{{Label: "http"}},
   147  								},
   148  							},
   149  						},
   150  					},
   151  				},
   152  				Meta: map[string]string{
   153  					"elb_check_type":     "http",
   154  					"elb_check_interval": "30s",
   155  					"elb_check_min":      "3",
   156  				},
   157  			},
   158  		},
   159  		Meta: map[string]string{
   160  			"owner": "armon",
   161  		},
   162  	}
   163  
   164  	c := j.Copy()
   165  	if !reflect.DeepEqual(j, c) {
   166  		t.Fatalf("Copy() returned an unequal Job; got %v; want %v", c, j)
   167  	}
   168  }
   169  
   170  func TestJob_IsPeriodic(t *testing.T) {
   171  	j := &Job{
   172  		Type: JobTypeService,
   173  		Periodic: &PeriodicConfig{
   174  			Enabled: true,
   175  		},
   176  	}
   177  	if !j.IsPeriodic() {
   178  		t.Fatalf("IsPeriodic() returned false on periodic job")
   179  	}
   180  
   181  	j = &Job{
   182  		Type: JobTypeService,
   183  	}
   184  	if j.IsPeriodic() {
   185  		t.Fatalf("IsPeriodic() returned true on non-periodic job")
   186  	}
   187  }
   188  
   189  func TestTaskGroup_Validate(t *testing.T) {
   190  	tg := &TaskGroup{
   191  		RestartPolicy: &RestartPolicy{
   192  			Interval:         5 * time.Minute,
   193  			Delay:            10 * time.Second,
   194  			Attempts:         10,
   195  			RestartOnSuccess: true,
   196  			Mode:             RestartPolicyModeDelay,
   197  		},
   198  	}
   199  	err := tg.Validate()
   200  	mErr := err.(*multierror.Error)
   201  	if !strings.Contains(mErr.Errors[0].Error(), "group name") {
   202  		t.Fatalf("err: %s", err)
   203  	}
   204  	if !strings.Contains(mErr.Errors[1].Error(), "count must be positive") {
   205  		t.Fatalf("err: %s", err)
   206  	}
   207  	if !strings.Contains(mErr.Errors[2].Error(), "Missing tasks") {
   208  		t.Fatalf("err: %s", err)
   209  	}
   210  
   211  	tg = &TaskGroup{
   212  		Name:  "web",
   213  		Count: 1,
   214  		Tasks: []*Task{
   215  			&Task{Name: "web"},
   216  			&Task{Name: "web"},
   217  			&Task{},
   218  		},
   219  		RestartPolicy: &RestartPolicy{
   220  			Interval:         5 * time.Minute,
   221  			Delay:            10 * time.Second,
   222  			Attempts:         10,
   223  			RestartOnSuccess: true,
   224  			Mode:             RestartPolicyModeDelay,
   225  		},
   226  	}
   227  	err = tg.Validate()
   228  	mErr = err.(*multierror.Error)
   229  	if !strings.Contains(mErr.Errors[0].Error(), "2 redefines 'web' from task 1") {
   230  		t.Fatalf("err: %s", err)
   231  	}
   232  	if !strings.Contains(mErr.Errors[1].Error(), "Task 3 missing name") {
   233  		t.Fatalf("err: %s", err)
   234  	}
   235  	if !strings.Contains(mErr.Errors[2].Error(), "Task 1 validation failed") {
   236  		t.Fatalf("err: %s", err)
   237  	}
   238  }
   239  
   240  func TestTask_Validate(t *testing.T) {
   241  	task := &Task{}
   242  	err := task.Validate()
   243  	mErr := err.(*multierror.Error)
   244  	if !strings.Contains(mErr.Errors[0].Error(), "task name") {
   245  		t.Fatalf("err: %s", err)
   246  	}
   247  	if !strings.Contains(mErr.Errors[1].Error(), "task driver") {
   248  		t.Fatalf("err: %s", err)
   249  	}
   250  	if !strings.Contains(mErr.Errors[2].Error(), "task resources") {
   251  		t.Fatalf("err: %s", err)
   252  	}
   253  
   254  	task = &Task{
   255  		Name:      "web",
   256  		Driver:    "docker",
   257  		Resources: &Resources{},
   258  	}
   259  	err = task.Validate()
   260  	if err != nil {
   261  		t.Fatalf("err: %s", err)
   262  	}
   263  }
   264  
   265  func TestConstraint_Validate(t *testing.T) {
   266  	c := &Constraint{}
   267  	err := c.Validate()
   268  	mErr := err.(*multierror.Error)
   269  	if !strings.Contains(mErr.Errors[0].Error(), "Missing constraint operand") {
   270  		t.Fatalf("err: %s", err)
   271  	}
   272  
   273  	c = &Constraint{
   274  		LTarget: "$attr.kernel.name",
   275  		RTarget: "linux",
   276  		Operand: "=",
   277  	}
   278  	err = c.Validate()
   279  	if err != nil {
   280  		t.Fatalf("err: %v", err)
   281  	}
   282  
   283  	// Perform additional regexp validation
   284  	c.Operand = ConstraintRegex
   285  	c.RTarget = "(foo"
   286  	err = c.Validate()
   287  	mErr = err.(*multierror.Error)
   288  	if !strings.Contains(mErr.Errors[0].Error(), "missing closing") {
   289  		t.Fatalf("err: %s", err)
   290  	}
   291  
   292  	// Perform version validation
   293  	c.Operand = ConstraintVersion
   294  	c.RTarget = "~> foo"
   295  	err = c.Validate()
   296  	mErr = err.(*multierror.Error)
   297  	if !strings.Contains(mErr.Errors[0].Error(), "Malformed constraint") {
   298  		t.Fatalf("err: %s", err)
   299  	}
   300  }
   301  
   302  func TestResource_NetIndex(t *testing.T) {
   303  	r := &Resources{
   304  		Networks: []*NetworkResource{
   305  			&NetworkResource{Device: "eth0"},
   306  			&NetworkResource{Device: "lo0"},
   307  			&NetworkResource{Device: ""},
   308  		},
   309  	}
   310  	if idx := r.NetIndex(&NetworkResource{Device: "eth0"}); idx != 0 {
   311  		t.Fatalf("Bad: %d", idx)
   312  	}
   313  	if idx := r.NetIndex(&NetworkResource{Device: "lo0"}); idx != 1 {
   314  		t.Fatalf("Bad: %d", idx)
   315  	}
   316  	if idx := r.NetIndex(&NetworkResource{Device: "eth1"}); idx != -1 {
   317  		t.Fatalf("Bad: %d", idx)
   318  	}
   319  }
   320  
   321  func TestResource_Superset(t *testing.T) {
   322  	r1 := &Resources{
   323  		CPU:      2000,
   324  		MemoryMB: 2048,
   325  		DiskMB:   10000,
   326  		IOPS:     100,
   327  	}
   328  	r2 := &Resources{
   329  		CPU:      2000,
   330  		MemoryMB: 1024,
   331  		DiskMB:   5000,
   332  		IOPS:     50,
   333  	}
   334  
   335  	if s, _ := r1.Superset(r1); !s {
   336  		t.Fatalf("bad")
   337  	}
   338  	if s, _ := r1.Superset(r2); !s {
   339  		t.Fatalf("bad")
   340  	}
   341  	if s, _ := r2.Superset(r1); s {
   342  		t.Fatalf("bad")
   343  	}
   344  	if s, _ := r2.Superset(r2); !s {
   345  		t.Fatalf("bad")
   346  	}
   347  }
   348  
   349  func TestResource_Add(t *testing.T) {
   350  	r1 := &Resources{
   351  		CPU:      2000,
   352  		MemoryMB: 2048,
   353  		DiskMB:   10000,
   354  		IOPS:     100,
   355  		Networks: []*NetworkResource{
   356  			&NetworkResource{
   357  				CIDR:          "10.0.0.0/8",
   358  				MBits:         100,
   359  				ReservedPorts: []Port{{"ssh", 22}},
   360  			},
   361  		},
   362  	}
   363  	r2 := &Resources{
   364  		CPU:      2000,
   365  		MemoryMB: 1024,
   366  		DiskMB:   5000,
   367  		IOPS:     50,
   368  		Networks: []*NetworkResource{
   369  			&NetworkResource{
   370  				IP:            "10.0.0.1",
   371  				MBits:         50,
   372  				ReservedPorts: []Port{{"web", 80}},
   373  			},
   374  		},
   375  	}
   376  
   377  	err := r1.Add(r2)
   378  	if err != nil {
   379  		t.Fatalf("Err: %v", err)
   380  	}
   381  
   382  	expect := &Resources{
   383  		CPU:      3000,
   384  		MemoryMB: 3072,
   385  		DiskMB:   15000,
   386  		IOPS:     150,
   387  		Networks: []*NetworkResource{
   388  			&NetworkResource{
   389  				CIDR:          "10.0.0.0/8",
   390  				MBits:         150,
   391  				ReservedPorts: []Port{{"ssh", 22}, {"web", 80}},
   392  			},
   393  		},
   394  	}
   395  
   396  	if !reflect.DeepEqual(expect.Networks, r1.Networks) {
   397  		t.Fatalf("bad: %#v %#v", expect, r1)
   398  	}
   399  }
   400  
   401  func TestResource_Add_Network(t *testing.T) {
   402  	r1 := &Resources{}
   403  	r2 := &Resources{
   404  		Networks: []*NetworkResource{
   405  			&NetworkResource{
   406  				MBits:        50,
   407  				DynamicPorts: []Port{{"http", 0}, {"https", 0}},
   408  			},
   409  		},
   410  	}
   411  	r3 := &Resources{
   412  		Networks: []*NetworkResource{
   413  			&NetworkResource{
   414  				MBits:        25,
   415  				DynamicPorts: []Port{{"admin", 0}},
   416  			},
   417  		},
   418  	}
   419  
   420  	err := r1.Add(r2)
   421  	if err != nil {
   422  		t.Fatalf("Err: %v", err)
   423  	}
   424  	err = r1.Add(r3)
   425  	if err != nil {
   426  		t.Fatalf("Err: %v", err)
   427  	}
   428  
   429  	expect := &Resources{
   430  		Networks: []*NetworkResource{
   431  			&NetworkResource{
   432  				MBits:        75,
   433  				DynamicPorts: []Port{{"http", 0}, {"https", 0}, {"admin", 0}},
   434  			},
   435  		},
   436  	}
   437  
   438  	if !reflect.DeepEqual(expect.Networks, r1.Networks) {
   439  		t.Fatalf("bad: %#v %#v", expect.Networks[0], r1.Networks[0])
   440  	}
   441  }
   442  
   443  func TestEncodeDecode(t *testing.T) {
   444  	type FooRequest struct {
   445  		Foo string
   446  		Bar int
   447  		Baz bool
   448  	}
   449  	arg := &FooRequest{
   450  		Foo: "test",
   451  		Bar: 42,
   452  		Baz: true,
   453  	}
   454  	buf, err := Encode(1, arg)
   455  	if err != nil {
   456  		t.Fatalf("err: %v", err)
   457  	}
   458  
   459  	var out FooRequest
   460  	err = Decode(buf[1:], &out)
   461  	if err != nil {
   462  		t.Fatalf("err: %v", err)
   463  	}
   464  
   465  	if !reflect.DeepEqual(arg, &out) {
   466  		t.Fatalf("bad: %#v %#v", arg, out)
   467  	}
   468  }
   469  
   470  func TestInvalidServiceCheck(t *testing.T) {
   471  	s := Service{
   472  		Name:      "service-name",
   473  		PortLabel: "bar",
   474  		Checks: []*ServiceCheck{
   475  			{
   476  
   477  				Name: "check-name",
   478  				Type: "lol",
   479  			},
   480  		},
   481  	}
   482  	if err := s.Validate(); err == nil {
   483  		t.Fatalf("Service should be invalid")
   484  	}
   485  }
   486  
   487  func TestDistinctCheckID(t *testing.T) {
   488  	c1 := ServiceCheck{
   489  		Name:     "web-health",
   490  		Type:     "http",
   491  		Path:     "/health",
   492  		Interval: 2 * time.Second,
   493  		Timeout:  3 * time.Second,
   494  	}
   495  	c2 := ServiceCheck{
   496  		Name:     "web-health",
   497  		Type:     "http",
   498  		Path:     "/health1",
   499  		Interval: 2 * time.Second,
   500  		Timeout:  3 * time.Second,
   501  	}
   502  
   503  	c3 := ServiceCheck{
   504  		Name:     "web-health",
   505  		Type:     "http",
   506  		Path:     "/health",
   507  		Interval: 4 * time.Second,
   508  		Timeout:  3 * time.Second,
   509  	}
   510  	serviceID := "123"
   511  	c1Hash := c1.Hash(serviceID)
   512  	c2Hash := c2.Hash(serviceID)
   513  	c3Hash := c3.Hash(serviceID)
   514  
   515  	if c1Hash == c2Hash || c1Hash == c3Hash || c3Hash == c2Hash {
   516  		t.Fatalf("Checks need to be uniq c1: %s, c2: %s, c3: %s", c1Hash, c2Hash, c3Hash)
   517  	}
   518  
   519  }
   520  
   521  func TestService_InitFields(t *testing.T) {
   522  	job := "example"
   523  	taskGroup := "cache"
   524  	task := "redis"
   525  
   526  	s := Service{
   527  		Name: "${TASK}-db",
   528  	}
   529  
   530  	s.InitFields(job, taskGroup, task)
   531  	if s.Name != "redis-db" {
   532  		t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name)
   533  	}
   534  
   535  	s.Name = "db"
   536  	s.InitFields(job, taskGroup, task)
   537  	if s.Name != "db" {
   538  		t.Fatalf("Expected name: %v, Actual: %v", "redis-db", s.Name)
   539  	}
   540  
   541  	s.Name = "${JOB}-${TASKGROUP}-${TASK}-db"
   542  	s.InitFields(job, taskGroup, task)
   543  	if s.Name != "example-cache-redis-db" {
   544  		t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name)
   545  	}
   546  
   547  	s.Name = "${BASE}-db"
   548  	s.InitFields(job, taskGroup, task)
   549  	if s.Name != "example-cache-redis-db" {
   550  		t.Fatalf("Expected name: %v, Actual: %v", "expample-cache-redis-db", s.Name)
   551  	}
   552  
   553  }
   554  
   555  func TestJob_ExpandServiceNames(t *testing.T) {
   556  	j := &Job{
   557  		Name: "my-job",
   558  		TaskGroups: []*TaskGroup{
   559  			&TaskGroup{
   560  				Name: "web",
   561  				Tasks: []*Task{
   562  					{
   563  						Name: "frontend",
   564  						Services: []*Service{
   565  							{
   566  								Name: "${BASE}-default",
   567  							},
   568  							{
   569  								Name: "jmx",
   570  							},
   571  						},
   572  					},
   573  				},
   574  			},
   575  			&TaskGroup{
   576  				Name: "admin",
   577  				Tasks: []*Task{
   578  					{
   579  						Name: "admin-web",
   580  					},
   581  				},
   582  			},
   583  		},
   584  	}
   585  
   586  	j.InitFields()
   587  
   588  	service1Name := j.TaskGroups[0].Tasks[0].Services[0].Name
   589  	if service1Name != "my-job-web-frontend-default" {
   590  		t.Fatalf("Expected Service Name: %s, Actual: %s", "my-job-web-frontend-default", service1Name)
   591  	}
   592  
   593  	service2Name := j.TaskGroups[0].Tasks[0].Services[1].Name
   594  	if service2Name != "jmx" {
   595  		t.Fatalf("Expected Service Name: %s, Actual: %s", "jmx", service2Name)
   596  	}
   597  
   598  }
   599  
   600  func TestPeriodicConfig_EnabledInvalid(t *testing.T) {
   601  	// Create a config that is enabled but with no interval specified.
   602  	p := &PeriodicConfig{Enabled: true}
   603  	if err := p.Validate(); err == nil {
   604  		t.Fatal("Enabled PeriodicConfig with no spec or type shouldn't be valid")
   605  	}
   606  
   607  	// Create a config that is enabled, with a spec but no type specified.
   608  	p = &PeriodicConfig{Enabled: true, Spec: "foo"}
   609  	if err := p.Validate(); err == nil {
   610  		t.Fatal("Enabled PeriodicConfig with no spec type shouldn't be valid")
   611  	}
   612  
   613  	// Create a config that is enabled, with a spec type but no spec specified.
   614  	p = &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron}
   615  	if err := p.Validate(); err == nil {
   616  		t.Fatal("Enabled PeriodicConfig with no spec shouldn't be valid")
   617  	}
   618  }
   619  
   620  func TestPeriodicConfig_InvalidCron(t *testing.T) {
   621  	specs := []string{"foo", "* *", "@foo"}
   622  	for _, spec := range specs {
   623  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
   624  		if err := p.Validate(); err == nil {
   625  			t.Fatal("Invalid cron spec")
   626  		}
   627  	}
   628  }
   629  
   630  func TestPeriodicConfig_ValidCron(t *testing.T) {
   631  	specs := []string{"0 0 29 2 *", "@hourly", "0 0-15 * * *"}
   632  	for _, spec := range specs {
   633  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
   634  		if err := p.Validate(); err != nil {
   635  			t.Fatal("Passed valid cron")
   636  		}
   637  	}
   638  }
   639  
   640  func TestPeriodicConfig_NextCron(t *testing.T) {
   641  	from := time.Date(2009, time.November, 10, 23, 22, 30, 0, time.UTC)
   642  	specs := []string{"0 0 29 2 * 1980", "*/5 * * * *"}
   643  	expected := []time.Time{time.Time{}, time.Date(2009, time.November, 10, 23, 25, 0, 0, time.UTC)}
   644  	for i, spec := range specs {
   645  		p := &PeriodicConfig{Enabled: true, SpecType: PeriodicSpecCron, Spec: spec}
   646  		n := p.Next(from)
   647  		if expected[i] != n {
   648  			t.Fatalf("Next(%v) returned %v; want %v", from, n, expected[i])
   649  		}
   650  	}
   651  }