github.com/taylorchu/nomad@v0.5.3-rc1.0.20170407200202-db11e7dd7b55/jobspec/parse_test.go (about)

     1  package jobspec
     2  
     3  import (
     4  	"path/filepath"
     5  	"reflect"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/hashicorp/nomad/api"
    11  	"github.com/hashicorp/nomad/helper"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  	"github.com/kr/pretty"
    14  
    15  	capi "github.com/hashicorp/consul/api"
    16  )
    17  
    18  func TestParse(t *testing.T) {
    19  	cases := []struct {
    20  		File   string
    21  		Result *api.Job
    22  		Err    bool
    23  	}{
    24  		{
    25  			"basic.hcl",
    26  			&api.Job{
    27  				ID:          helper.StringToPtr("binstore-storagelocker"),
    28  				Name:        helper.StringToPtr("binstore-storagelocker"),
    29  				Type:        helper.StringToPtr("batch"),
    30  				Priority:    helper.IntToPtr(52),
    31  				AllAtOnce:   helper.BoolToPtr(true),
    32  				Datacenters: []string{"us2", "eu1"},
    33  				Region:      helper.StringToPtr("fooregion"),
    34  				VaultToken:  helper.StringToPtr("foo"),
    35  
    36  				Meta: map[string]string{
    37  					"foo": "bar",
    38  				},
    39  
    40  				Constraints: []*api.Constraint{
    41  					&api.Constraint{
    42  						LTarget: "kernel.os",
    43  						RTarget: "windows",
    44  						Operand: "=",
    45  					},
    46  				},
    47  
    48  				Update: &api.UpdateStrategy{
    49  					Stagger:     60 * time.Second,
    50  					MaxParallel: 2,
    51  				},
    52  
    53  				TaskGroups: []*api.TaskGroup{
    54  					&api.TaskGroup{
    55  						Name: helper.StringToPtr("outside"),
    56  						Tasks: []*api.Task{
    57  							&api.Task{
    58  								Name:   "outside",
    59  								Driver: "java",
    60  								Config: map[string]interface{}{
    61  									"jar_path": "s3://my-cool-store/foo.jar",
    62  								},
    63  								Meta: map[string]string{
    64  									"my-cool-key": "foobar",
    65  								},
    66  							},
    67  						},
    68  					},
    69  
    70  					&api.TaskGroup{
    71  						Name:  helper.StringToPtr("binsl"),
    72  						Count: helper.IntToPtr(5),
    73  						Constraints: []*api.Constraint{
    74  							&api.Constraint{
    75  								LTarget: "kernel.os",
    76  								RTarget: "linux",
    77  								Operand: "=",
    78  							},
    79  						},
    80  						Meta: map[string]string{
    81  							"elb_mode":     "tcp",
    82  							"elb_interval": "10",
    83  							"elb_checks":   "3",
    84  						},
    85  						RestartPolicy: &api.RestartPolicy{
    86  							Interval: helper.TimeToPtr(10 * time.Minute),
    87  							Attempts: helper.IntToPtr(5),
    88  							Delay:    helper.TimeToPtr(15 * time.Second),
    89  							Mode:     helper.StringToPtr("delay"),
    90  						},
    91  						EphemeralDisk: &api.EphemeralDisk{
    92  							Sticky: helper.BoolToPtr(true),
    93  							SizeMB: helper.IntToPtr(150),
    94  						},
    95  						Tasks: []*api.Task{
    96  							&api.Task{
    97  								Name:   "binstore",
    98  								Driver: "docker",
    99  								User:   "bob",
   100  								Config: map[string]interface{}{
   101  									"image": "hashicorp/binstore",
   102  									"labels": []map[string]interface{}{
   103  										map[string]interface{}{
   104  											"FOO": "bar",
   105  										},
   106  									},
   107  								},
   108  								Services: []*api.Service{
   109  									{
   110  										Tags:      []string{"foo", "bar"},
   111  										PortLabel: "http",
   112  										Checks: []api.ServiceCheck{
   113  											{
   114  												Name:      "check-name",
   115  												Type:      "tcp",
   116  												PortLabel: "admin",
   117  												Interval:  10 * time.Second,
   118  												Timeout:   2 * time.Second,
   119  											},
   120  										},
   121  									},
   122  								},
   123  								Env: map[string]string{
   124  									"HELLO": "world",
   125  									"LOREM": "ipsum",
   126  								},
   127  								Resources: &api.Resources{
   128  									CPU:      helper.IntToPtr(500),
   129  									MemoryMB: helper.IntToPtr(128),
   130  									Networks: []*api.NetworkResource{
   131  										&api.NetworkResource{
   132  											MBits:         helper.IntToPtr(100),
   133  											ReservedPorts: []api.Port{{Label: "one", Value: 1}, {Label: "two", Value: 2}, {Label: "three", Value: 3}},
   134  											DynamicPorts:  []api.Port{{Label: "http", Value: 0}, {Label: "https", Value: 0}, {Label: "admin", Value: 0}},
   135  										},
   136  									},
   137  								},
   138  								KillTimeout: helper.TimeToPtr(22 * time.Second),
   139  								LogConfig: &api.LogConfig{
   140  									MaxFiles:      helper.IntToPtr(14),
   141  									MaxFileSizeMB: helper.IntToPtr(101),
   142  								},
   143  								Artifacts: []*api.TaskArtifact{
   144  									{
   145  										GetterSource: helper.StringToPtr("http://foo.com/artifact"),
   146  										GetterOptions: map[string]string{
   147  											"checksum": "md5:b8a4f3f72ecab0510a6a31e997461c5f",
   148  										},
   149  									},
   150  									{
   151  										GetterSource: helper.StringToPtr("http://bar.com/artifact"),
   152  										RelativeDest: helper.StringToPtr("test/foo/"),
   153  										GetterOptions: map[string]string{
   154  											"checksum": "md5:ff1cc0d3432dad54d607c1505fb7245c",
   155  										},
   156  									},
   157  								},
   158  								Vault: &api.Vault{
   159  									Policies:   []string{"foo", "bar"},
   160  									Env:        helper.BoolToPtr(true),
   161  									ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart),
   162  								},
   163  								Templates: []*api.Template{
   164  									{
   165  										SourcePath:   helper.StringToPtr("foo"),
   166  										DestPath:     helper.StringToPtr("foo"),
   167  										ChangeMode:   helper.StringToPtr("foo"),
   168  										ChangeSignal: helper.StringToPtr("foo"),
   169  										Splay:        helper.TimeToPtr(10 * time.Second),
   170  										Perms:        helper.StringToPtr("0644"),
   171  									},
   172  									{
   173  										SourcePath: helper.StringToPtr("bar"),
   174  										DestPath:   helper.StringToPtr("bar"),
   175  										ChangeMode: helper.StringToPtr(structs.TemplateChangeModeRestart),
   176  										Splay:      helper.TimeToPtr(5 * time.Second),
   177  										Perms:      helper.StringToPtr("777"),
   178  										LeftDelim:  helper.StringToPtr("--"),
   179  										RightDelim: helper.StringToPtr("__"),
   180  									},
   181  								},
   182  								Leader: true,
   183  							},
   184  							&api.Task{
   185  								Name:   "storagelocker",
   186  								Driver: "docker",
   187  								User:   "",
   188  								Config: map[string]interface{}{
   189  									"image": "hashicorp/storagelocker",
   190  								},
   191  								Resources: &api.Resources{
   192  									CPU:      helper.IntToPtr(500),
   193  									MemoryMB: helper.IntToPtr(128),
   194  									IOPS:     helper.IntToPtr(30),
   195  								},
   196  								Constraints: []*api.Constraint{
   197  									&api.Constraint{
   198  										LTarget: "kernel.arch",
   199  										RTarget: "amd64",
   200  										Operand: "=",
   201  									},
   202  								},
   203  								Vault: &api.Vault{
   204  									Policies:     []string{"foo", "bar"},
   205  									Env:          helper.BoolToPtr(false),
   206  									ChangeMode:   helper.StringToPtr(structs.VaultChangeModeSignal),
   207  									ChangeSignal: helper.StringToPtr("SIGUSR1"),
   208  								},
   209  							},
   210  						},
   211  					},
   212  				},
   213  			},
   214  			false,
   215  		},
   216  
   217  		{
   218  			"multi-network.hcl",
   219  			nil,
   220  			true,
   221  		},
   222  
   223  		{
   224  			"multi-resource.hcl",
   225  			nil,
   226  			true,
   227  		},
   228  
   229  		{
   230  			"multi-vault.hcl",
   231  			nil,
   232  			true,
   233  		},
   234  
   235  		{
   236  			"default-job.hcl",
   237  			&api.Job{
   238  				ID:   helper.StringToPtr("foo"),
   239  				Name: helper.StringToPtr("foo"),
   240  			},
   241  			false,
   242  		},
   243  
   244  		{
   245  			"version-constraint.hcl",
   246  			&api.Job{
   247  				ID:   helper.StringToPtr("foo"),
   248  				Name: helper.StringToPtr("foo"),
   249  				Constraints: []*api.Constraint{
   250  					&api.Constraint{
   251  						LTarget: "$attr.kernel.version",
   252  						RTarget: "~> 3.2",
   253  						Operand: structs.ConstraintVersion,
   254  					},
   255  				},
   256  			},
   257  			false,
   258  		},
   259  
   260  		{
   261  			"regexp-constraint.hcl",
   262  			&api.Job{
   263  				ID:   helper.StringToPtr("foo"),
   264  				Name: helper.StringToPtr("foo"),
   265  				Constraints: []*api.Constraint{
   266  					&api.Constraint{
   267  						LTarget: "$attr.kernel.version",
   268  						RTarget: "[0-9.]+",
   269  						Operand: structs.ConstraintRegex,
   270  					},
   271  				},
   272  			},
   273  			false,
   274  		},
   275  
   276  		{
   277  			"set-contains-constraint.hcl",
   278  			&api.Job{
   279  				ID:   helper.StringToPtr("foo"),
   280  				Name: helper.StringToPtr("foo"),
   281  				Constraints: []*api.Constraint{
   282  					&api.Constraint{
   283  						LTarget: "$meta.data",
   284  						RTarget: "foo,bar,baz",
   285  						Operand: structs.ConstraintSetContains,
   286  					},
   287  				},
   288  			},
   289  			false,
   290  		},
   291  
   292  		{
   293  			"distinctHosts-constraint.hcl",
   294  			&api.Job{
   295  				ID:   helper.StringToPtr("foo"),
   296  				Name: helper.StringToPtr("foo"),
   297  				Constraints: []*api.Constraint{
   298  					&api.Constraint{
   299  						Operand: structs.ConstraintDistinctHosts,
   300  					},
   301  				},
   302  			},
   303  			false,
   304  		},
   305  
   306  		{
   307  			"distinctProperty-constraint.hcl",
   308  			&api.Job{
   309  				ID:   helper.StringToPtr("foo"),
   310  				Name: helper.StringToPtr("foo"),
   311  				Constraints: []*api.Constraint{
   312  					&api.Constraint{
   313  						Operand: structs.ConstraintDistinctProperty,
   314  						LTarget: "${meta.rack}",
   315  					},
   316  				},
   317  			},
   318  			false,
   319  		},
   320  
   321  		{
   322  			"periodic-cron.hcl",
   323  			&api.Job{
   324  				ID:   helper.StringToPtr("foo"),
   325  				Name: helper.StringToPtr("foo"),
   326  				Periodic: &api.PeriodicConfig{
   327  					SpecType:        helper.StringToPtr(api.PeriodicSpecCron),
   328  					Spec:            helper.StringToPtr("*/5 * * *"),
   329  					ProhibitOverlap: helper.BoolToPtr(true),
   330  					TimeZone:        helper.StringToPtr("Europe/Minsk"),
   331  				},
   332  			},
   333  			false,
   334  		},
   335  
   336  		{
   337  			"specify-job.hcl",
   338  			&api.Job{
   339  				ID:   helper.StringToPtr("job1"),
   340  				Name: helper.StringToPtr("My Job"),
   341  			},
   342  			false,
   343  		},
   344  
   345  		{
   346  			"task-nested-config.hcl",
   347  			&api.Job{
   348  				ID:   helper.StringToPtr("foo"),
   349  				Name: helper.StringToPtr("foo"),
   350  				TaskGroups: []*api.TaskGroup{
   351  					&api.TaskGroup{
   352  						Name: helper.StringToPtr("bar"),
   353  						Tasks: []*api.Task{
   354  							&api.Task{
   355  								Name:   "bar",
   356  								Driver: "docker",
   357  								Config: map[string]interface{}{
   358  									"image": "hashicorp/image",
   359  									"port_map": []map[string]interface{}{
   360  										map[string]interface{}{
   361  											"db": 1234,
   362  										},
   363  									},
   364  								},
   365  							},
   366  						},
   367  					},
   368  				},
   369  			},
   370  			false,
   371  		},
   372  
   373  		{
   374  			"bad-artifact.hcl",
   375  			nil,
   376  			true,
   377  		},
   378  
   379  		{
   380  			"artifacts.hcl",
   381  			&api.Job{
   382  				ID:   helper.StringToPtr("binstore-storagelocker"),
   383  				Name: helper.StringToPtr("binstore-storagelocker"),
   384  				TaskGroups: []*api.TaskGroup{
   385  					&api.TaskGroup{
   386  						Name: helper.StringToPtr("binsl"),
   387  						Tasks: []*api.Task{
   388  							&api.Task{
   389  								Name:   "binstore",
   390  								Driver: "docker",
   391  								Artifacts: []*api.TaskArtifact{
   392  									{
   393  										GetterSource:  helper.StringToPtr("http://foo.com/bar"),
   394  										GetterOptions: map[string]string{"foo": "bar"},
   395  										RelativeDest:  helper.StringToPtr(""),
   396  									},
   397  									{
   398  										GetterSource:  helper.StringToPtr("http://foo.com/baz"),
   399  										GetterOptions: nil,
   400  										RelativeDest:  nil,
   401  									},
   402  									{
   403  										GetterSource:  helper.StringToPtr("http://foo.com/bam"),
   404  										GetterOptions: nil,
   405  										RelativeDest:  helper.StringToPtr("var/foo"),
   406  									},
   407  								},
   408  							},
   409  						},
   410  					},
   411  				},
   412  			},
   413  			false,
   414  		},
   415  		{
   416  			"service-check-initial-status.hcl",
   417  			&api.Job{
   418  				ID:   helper.StringToPtr("check_initial_status"),
   419  				Name: helper.StringToPtr("check_initial_status"),
   420  				Type: helper.StringToPtr("service"),
   421  				TaskGroups: []*api.TaskGroup{
   422  					&api.TaskGroup{
   423  						Name:  helper.StringToPtr("group"),
   424  						Count: helper.IntToPtr(1),
   425  						Tasks: []*api.Task{
   426  							&api.Task{
   427  								Name: "task",
   428  								Services: []*api.Service{
   429  									{
   430  										Tags:      []string{"foo", "bar"},
   431  										PortLabel: "http",
   432  										Checks: []api.ServiceCheck{
   433  											{
   434  												Name:          "check-name",
   435  												Type:          "http",
   436  												Interval:      10 * time.Second,
   437  												Timeout:       2 * time.Second,
   438  												InitialStatus: capi.HealthPassing,
   439  											},
   440  										},
   441  									},
   442  								},
   443  							},
   444  						},
   445  					},
   446  				},
   447  			},
   448  			false,
   449  		},
   450  		{
   451  			// TODO This should be pushed into the API
   452  			"vault_inheritance.hcl",
   453  			&api.Job{
   454  				ID:   helper.StringToPtr("example"),
   455  				Name: helper.StringToPtr("example"),
   456  				TaskGroups: []*api.TaskGroup{
   457  					&api.TaskGroup{
   458  						Name: helper.StringToPtr("cache"),
   459  						Tasks: []*api.Task{
   460  							&api.Task{
   461  								Name: "redis",
   462  								Vault: &api.Vault{
   463  									Policies:   []string{"group"},
   464  									Env:        helper.BoolToPtr(true),
   465  									ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart),
   466  								},
   467  							},
   468  							&api.Task{
   469  								Name: "redis2",
   470  								Vault: &api.Vault{
   471  									Policies:   []string{"task"},
   472  									Env:        helper.BoolToPtr(false),
   473  									ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart),
   474  								},
   475  							},
   476  						},
   477  					},
   478  					&api.TaskGroup{
   479  						Name: helper.StringToPtr("cache2"),
   480  						Tasks: []*api.Task{
   481  							&api.Task{
   482  								Name: "redis",
   483  								Vault: &api.Vault{
   484  									Policies:   []string{"job"},
   485  									Env:        helper.BoolToPtr(true),
   486  									ChangeMode: helper.StringToPtr(structs.VaultChangeModeRestart),
   487  								},
   488  							},
   489  						},
   490  					},
   491  				},
   492  			},
   493  			false,
   494  		},
   495  		{
   496  			"parameterized_job.hcl",
   497  			&api.Job{
   498  				ID:   helper.StringToPtr("parameterized_job"),
   499  				Name: helper.StringToPtr("parameterized_job"),
   500  
   501  				ParameterizedJob: &api.ParameterizedJobConfig{
   502  					Payload:      "required",
   503  					MetaRequired: []string{"foo", "bar"},
   504  					MetaOptional: []string{"baz", "bam"},
   505  				},
   506  
   507  				TaskGroups: []*api.TaskGroup{
   508  					{
   509  						Name: helper.StringToPtr("foo"),
   510  						Tasks: []*api.Task{
   511  							{
   512  								Name:   "bar",
   513  								Driver: "docker",
   514  								DispatchPayload: &api.DispatchPayloadConfig{
   515  									File: "foo/bar",
   516  								},
   517  							},
   518  						},
   519  					},
   520  				},
   521  			},
   522  			false,
   523  		},
   524  	}
   525  
   526  	for _, tc := range cases {
   527  		t.Logf("Testing parse: %s", tc.File)
   528  
   529  		path, err := filepath.Abs(filepath.Join("./test-fixtures", tc.File))
   530  		if err != nil {
   531  			t.Fatalf("file: %s\n\n%s", tc.File, err)
   532  			continue
   533  		}
   534  
   535  		actual, err := ParseFile(path)
   536  		if (err != nil) != tc.Err {
   537  			t.Fatalf("file: %s\n\n%s", tc.File, err)
   538  			continue
   539  		}
   540  
   541  		if !reflect.DeepEqual(actual, tc.Result) {
   542  			for _, d := range pretty.Diff(actual, tc.Result) {
   543  				t.Logf(d)
   544  			}
   545  			t.Fatalf("file: %s", tc.File)
   546  		}
   547  	}
   548  }
   549  
   550  func TestBadPorts(t *testing.T) {
   551  	path, err := filepath.Abs(filepath.Join("./test-fixtures", "bad-ports.hcl"))
   552  	if err != nil {
   553  		t.Fatalf("Can't get absolute path for file: %s", err)
   554  	}
   555  
   556  	_, err = ParseFile(path)
   557  
   558  	if !strings.Contains(err.Error(), errPortLabel.Error()) {
   559  		t.Fatalf("\nExpected error\n  %s\ngot\n  %v", errPortLabel, err)
   560  	}
   561  }
   562  
   563  func TestOverlappingPorts(t *testing.T) {
   564  	path, err := filepath.Abs(filepath.Join("./test-fixtures", "overlapping-ports.hcl"))
   565  	if err != nil {
   566  		t.Fatalf("Can't get absolute path for file: %s", err)
   567  	}
   568  
   569  	_, err = ParseFile(path)
   570  
   571  	if err == nil {
   572  		t.Fatalf("Expected an error")
   573  	}
   574  
   575  	if !strings.Contains(err.Error(), "found a port label collision") {
   576  		t.Fatalf("Expected collision error; got %v", err)
   577  	}
   578  }
   579  
   580  func TestIncorrectKey(t *testing.T) {
   581  	path, err := filepath.Abs(filepath.Join("./test-fixtures", "basic_wrong_key.hcl"))
   582  	if err != nil {
   583  		t.Fatalf("Can't get absolute path for file: %s", err)
   584  	}
   585  
   586  	_, err = ParseFile(path)
   587  
   588  	if err == nil {
   589  		t.Fatalf("Expected an error")
   590  	}
   591  
   592  	if !strings.Contains(err.Error(), "* group: 'binsl', task: 'binstore', service: 'foo', check -> invalid key: nterval") {
   593  		t.Fatalf("Expected key error; got %v", err)
   594  	}
   595  }