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