open-nomad/jobspec/parse_test.go

654 lines
15 KiB
Go
Raw Normal View History

2015-09-15 00:43:42 +00:00
package jobspec
import (
"path/filepath"
"reflect"
"strings"
2015-09-15 00:43:42 +00:00
"testing"
"time"
2015-09-15 00:43:42 +00:00
"github.com/hashicorp/nomad/nomad/structs"
2016-08-16 22:22:26 +00:00
"github.com/hashicorp/consul/api"
2015-09-15 00:43:42 +00:00
)
func TestParse(t *testing.T) {
cases := []struct {
File string
Result *structs.Job
Err bool
}{
{
"basic.hcl",
&structs.Job{
ID: "binstore-storagelocker",
2015-09-15 00:43:42 +00:00
Name: "binstore-storagelocker",
Type: "service",
Priority: 50,
AllAtOnce: true,
Datacenters: []string{"us2", "eu1"},
2015-09-15 01:18:49 +00:00
Region: "global",
2016-08-11 23:54:30 +00:00
VaultToken: "foo",
2015-09-15 00:43:42 +00:00
2015-09-15 00:46:52 +00:00
Meta: map[string]string{
"foo": "bar",
},
2015-09-15 00:48:11 +00:00
Constraints: []*structs.Constraint{
&structs.Constraint{
LTarget: "kernel.os",
RTarget: "windows",
Operand: "=",
},
},
Update: structs.UpdateStrategy{
Stagger: 60 * time.Second,
MaxParallel: 2,
},
2015-09-15 00:43:42 +00:00
TaskGroups: []*structs.TaskGroup{
&structs.TaskGroup{
Name: "outside",
Count: 1,
EphemeralDisk: structs.DefaultEphemeralDisk(),
2015-09-15 00:43:42 +00:00
Tasks: []*structs.Task{
&structs.Task{
Name: "outside",
Driver: "java",
2015-11-15 08:10:48 +00:00
Config: map[string]interface{}{
"jar_path": "s3://my-cool-store/foo.jar",
2015-09-15 00:43:42 +00:00
},
Meta: map[string]string{
"my-cool-key": "foobar",
},
2016-02-12 06:33:41 +00:00
LogConfig: structs.DefaultLogConfig(),
2015-09-15 00:43:42 +00:00
},
},
},
&structs.TaskGroup{
Name: "binsl",
Count: 5,
Constraints: []*structs.Constraint{
&structs.Constraint{
LTarget: "kernel.os",
RTarget: "linux",
Operand: "=",
},
},
Meta: map[string]string{
"elb_mode": "tcp",
"elb_interval": "10",
"elb_checks": "3",
},
RestartPolicy: &structs.RestartPolicy{
Interval: 10 * time.Minute,
Attempts: 5,
Delay: 15 * time.Second,
Mode: "delay",
},
EphemeralDisk: &structs.EphemeralDisk{
Sticky: true,
SizeMB: 150,
},
2015-09-15 00:43:42 +00:00
Tasks: []*structs.Task{
&structs.Task{
Name: "binstore",
Driver: "docker",
User: "bob",
2015-11-15 08:10:48 +00:00
Config: map[string]interface{}{
2015-09-15 00:43:42 +00:00
"image": "hashicorp/binstore",
"labels": []map[string]interface{}{
map[string]interface{}{
"FOO": "bar",
},
},
2015-09-15 00:43:42 +00:00
},
2016-06-12 23:36:49 +00:00
Services: []*structs.Service{
2015-11-17 06:51:08 +00:00
{
2015-11-18 00:05:03 +00:00
Name: "binstore-storagelocker-binsl-binstore",
2015-11-17 06:51:08 +00:00
Tags: []string{"foo", "bar"},
PortLabel: "http",
2015-11-26 08:21:25 +00:00
Checks: []*structs.ServiceCheck{
2015-11-17 06:51:08 +00:00
{
Name: "check-name",
Type: "tcp",
PortLabel: "admin",
Interval: 10 * time.Second,
Timeout: 2 * time.Second,
2015-11-17 06:51:08 +00:00
},
},
},
},
Env: map[string]string{
"HELLO": "world",
"LOREM": "ipsum",
},
2015-09-15 00:43:42 +00:00
Resources: &structs.Resources{
CPU: 500,
MemoryMB: 128,
2016-02-03 01:39:01 +00:00
IOPS: 0,
2015-09-15 01:27:37 +00:00
Networks: []*structs.NetworkResource{
&structs.NetworkResource{
MBits: 100,
2015-11-15 08:10:48 +00:00
ReservedPorts: []structs.Port{{"one", 1}, {"two", 2}, {"three", 3}},
DynamicPorts: []structs.Port{{"http", 0}, {"https", 0}, {"admin", 0}},
2015-09-15 01:27:37 +00:00
},
},
2015-09-15 00:43:42 +00:00
},
KillTimeout: 22 * time.Second,
2016-02-05 07:28:01 +00:00
LogConfig: &structs.LogConfig{
MaxFiles: 10,
MaxFileSizeMB: 100,
},
2016-03-14 18:13:43 +00:00
Artifacts: []*structs.TaskArtifact{
{
GetterSource: "http://foo.com/artifact",
RelativeDest: "local/",
GetterOptions: map[string]string{
"checksum": "md5:b8a4f3f72ecab0510a6a31e997461c5f",
2016-03-14 18:13:43 +00:00
},
},
{
GetterSource: "http://bar.com/artifact",
RelativeDest: "local/",
GetterOptions: map[string]string{
"checksum": "md5:ff1cc0d3432dad54d607c1505fb7245c",
2016-03-14 18:13:43 +00:00
},
},
},
2016-08-09 23:07:45 +00:00
Vault: &structs.Vault{
Policies: []string{"foo", "bar"},
2016-09-20 20:22:29 +00:00
Env: true,
2016-08-09 23:07:45 +00:00
},
2016-09-26 22:23:26 +00:00
Templates: []*structs.Template{
{
SourcePath: "foo",
DestPath: "foo",
ChangeMode: "foo",
RestartSignal: "foo",
Splay: 10 * time.Second,
Once: true,
},
{
SourcePath: "bar",
DestPath: "bar",
ChangeMode: structs.TemplateChangeModeRestart,
RestartSignal: "",
Splay: 5 * time.Second,
Once: false,
},
},
2015-09-15 00:43:42 +00:00
},
&structs.Task{
Name: "storagelocker",
Driver: "docker",
User: "",
2015-11-15 08:10:48 +00:00
Config: map[string]interface{}{
2015-09-15 00:43:42 +00:00
"image": "hashicorp/storagelocker",
},
Resources: &structs.Resources{
CPU: 500,
MemoryMB: 128,
IOPS: 30,
2015-09-15 00:43:42 +00:00
},
2015-09-15 00:50:34 +00:00
Constraints: []*structs.Constraint{
&structs.Constraint{
LTarget: "kernel.arch",
RTarget: "amd64",
Operand: "=",
},
},
2016-02-12 06:33:41 +00:00
LogConfig: structs.DefaultLogConfig(),
2015-09-15 00:43:42 +00:00
},
},
},
},
},
false,
},
2015-09-15 01:30:26 +00:00
{
"multi-network.hcl",
nil,
true,
},
{
"multi-resource.hcl",
nil,
true,
},
2015-09-15 01:34:26 +00:00
2016-08-09 23:07:45 +00:00
{
"multi-vault.hcl",
nil,
true,
},
2015-09-15 01:34:26 +00:00
{
"default-job.hcl",
&structs.Job{
ID: "foo",
2015-09-15 01:34:26 +00:00
Name: "foo",
Priority: 50,
Region: "global",
Type: "service",
},
false,
},
{
"version-constraint.hcl",
&structs.Job{
ID: "foo",
Name: "foo",
Priority: 50,
Region: "global",
Type: "service",
Constraints: []*structs.Constraint{
&structs.Constraint{
LTarget: "$attr.kernel.version",
RTarget: "~> 3.2",
Operand: structs.ConstraintVersion,
},
},
},
false,
},
{
"regexp-constraint.hcl",
&structs.Job{
ID: "foo",
Name: "foo",
Priority: 50,
Region: "global",
Type: "service",
Constraints: []*structs.Constraint{
&structs.Constraint{
LTarget: "$attr.kernel.version",
RTarget: "[0-9.]+",
Operand: structs.ConstraintRegex,
},
},
},
false,
},
{
2015-10-23 00:40:41 +00:00
"distinctHosts-constraint.hcl",
&structs.Job{
ID: "foo",
Name: "foo",
Priority: 50,
Region: "global",
Type: "service",
Constraints: []*structs.Constraint{
&structs.Constraint{
Operand: structs.ConstraintDistinctHosts,
},
},
},
false,
},
2015-12-01 00:51:56 +00:00
{
"periodic-cron.hcl",
&structs.Job{
ID: "foo",
Name: "foo",
Priority: 50,
Region: "global",
Type: "service",
2015-12-01 16:58:36 +00:00
Periodic: &structs.PeriodicConfig{
Enabled: true,
SpecType: structs.PeriodicSpecCron,
Spec: "*/5 * * *",
ProhibitOverlap: true,
2015-12-01 00:51:56 +00:00
},
},
false,
},
{
"specify-job.hcl",
&structs.Job{
ID: "job1",
Name: "My Job",
Priority: 50,
Region: "global",
Type: "service",
},
false,
},
{
"task-nested-config.hcl",
&structs.Job{
Region: "global",
ID: "foo",
Name: "foo",
Type: "service",
Priority: 50,
TaskGroups: []*structs.TaskGroup{
&structs.TaskGroup{
Name: "bar",
Count: 1,
EphemeralDisk: structs.DefaultEphemeralDisk(),
Tasks: []*structs.Task{
&structs.Task{
Name: "bar",
Driver: "docker",
Config: map[string]interface{}{
"image": "hashicorp/image",
"port_map": []map[string]interface{}{
map[string]interface{}{
"db": 1234,
},
},
},
2016-02-05 07:28:01 +00:00
LogConfig: &structs.LogConfig{
MaxFiles: 10,
MaxFileSizeMB: 10,
},
},
},
},
},
},
false,
},
2016-03-16 03:21:52 +00:00
{
"bad-artifact.hcl",
nil,
true,
},
{
"artifacts.hcl",
&structs.Job{
ID: "binstore-storagelocker",
Name: "binstore-storagelocker",
Type: "service",
Priority: 50,
Region: "global",
TaskGroups: []*structs.TaskGroup{
&structs.TaskGroup{
Name: "binsl",
Count: 1,
EphemeralDisk: structs.DefaultEphemeralDisk(),
Tasks: []*structs.Task{
&structs.Task{
Name: "binstore",
Driver: "docker",
Resources: &structs.Resources{
CPU: 100,
MemoryMB: 10,
IOPS: 0,
},
LogConfig: &structs.LogConfig{
MaxFiles: 10,
MaxFileSizeMB: 10,
},
Artifacts: []*structs.TaskArtifact{
{
GetterSource: "http://foo.com/bar",
2016-06-10 19:28:27 +00:00
GetterOptions: map[string]string{"foo": "bar"},
RelativeDest: "",
},
{
GetterSource: "http://foo.com/baz",
2016-06-10 19:28:27 +00:00
GetterOptions: nil,
RelativeDest: "local/",
},
{
GetterSource: "http://foo.com/bam",
2016-06-10 19:28:27 +00:00
GetterOptions: nil,
RelativeDest: "var/foo",
},
},
},
},
},
},
},
false,
},
2016-08-16 21:34:36 +00:00
{
"service-check-initial-status.hcl",
&structs.Job{
ID: "check_initial_status",
Name: "check_initial_status",
Type: "service",
Priority: 50,
Region: "global",
TaskGroups: []*structs.TaskGroup{
&structs.TaskGroup{
Name: "group",
Count: 1,
EphemeralDisk: structs.DefaultEphemeralDisk(),
2016-08-16 21:34:36 +00:00
Tasks: []*structs.Task{
&structs.Task{
Name: "task",
Services: []*structs.Service{
{
Name: "check_initial_status-group-task",
Tags: []string{"foo", "bar"},
PortLabel: "http",
Checks: []*structs.ServiceCheck{
{
Name: "check-name",
Type: "http",
Interval: 10 * time.Second,
Timeout: 2 * time.Second,
2016-08-16 22:22:26 +00:00
InitialStatus: api.HealthPassing,
2016-08-16 21:34:36 +00:00
},
},
},
},
LogConfig: structs.DefaultLogConfig(),
},
},
},
},
},
false,
},
2016-09-21 18:18:44 +00:00
{
"vault_inheritance.hcl",
&structs.Job{
ID: "example",
Name: "example",
Type: "service",
Priority: 50,
Region: "global",
TaskGroups: []*structs.TaskGroup{
&structs.TaskGroup{
2016-09-26 22:23:26 +00:00
Name: "cache",
Count: 1,
EphemeralDisk: structs.DefaultEphemeralDisk(),
2016-09-21 18:18:44 +00:00
Tasks: []*structs.Task{
&structs.Task{
Name: "redis",
LogConfig: structs.DefaultLogConfig(),
Vault: &structs.Vault{
Policies: []string{"group"},
2016-09-20 20:22:29 +00:00
Env: true,
2016-09-21 18:18:44 +00:00
},
},
&structs.Task{
Name: "redis2",
LogConfig: structs.DefaultLogConfig(),
Vault: &structs.Vault{
Policies: []string{"task"},
2016-09-20 20:22:29 +00:00
Env: false,
2016-09-21 18:18:44 +00:00
},
},
},
},
&structs.TaskGroup{
2016-09-26 22:23:26 +00:00
Name: "cache2",
Count: 1,
EphemeralDisk: structs.DefaultEphemeralDisk(),
2016-09-21 18:18:44 +00:00
Tasks: []*structs.Task{
&structs.Task{
Name: "redis",
LogConfig: structs.DefaultLogConfig(),
Vault: &structs.Vault{
Policies: []string{"job"},
2016-09-20 20:22:29 +00:00
Env: true,
2016-09-21 18:18:44 +00:00
},
},
},
},
},
},
false,
},
2015-09-15 00:43:42 +00:00
}
for _, tc := range cases {
2015-11-09 06:57:39 +00:00
t.Logf("Testing parse: %s", tc.File)
2015-09-15 00:43:42 +00:00
path, err := filepath.Abs(filepath.Join("./test-fixtures", tc.File))
if err != nil {
t.Fatalf("file: %s\n\n%s", tc.File, err)
continue
}
actual, err := ParseFile(path)
if (err != nil) != tc.Err {
t.Fatalf("file: %s\n\n%s", tc.File, err)
continue
}
if !reflect.DeepEqual(actual, tc.Result) {
t.Fatalf("file: %s\n\n%#v\n\n%#v", tc.File, actual, tc.Result)
}
}
}
func TestBadConfigEmpty(t *testing.T) {
path, err := filepath.Abs(filepath.Join("./test-fixtures", "bad-config-empty.hcl"))
if err != nil {
t.Fatalf("Can't get absolute path for file: %s", err)
}
_, err = ParseFile(path)
if !strings.Contains(err.Error(), "field \"image\" is required, but no value was found") {
t.Fatalf("\nExpected error\n %s\ngot\n %v",
"field \"image\" is required, but no value was found",
err,
)
}
}
func TestBadConfigMissing(t *testing.T) {
path, err := filepath.Abs(filepath.Join("./test-fixtures", "bad-config-missing.hcl"))
if err != nil {
t.Fatalf("Can't get absolute path for file: %s", err)
}
_, err = ParseFile(path)
if !strings.Contains(err.Error(), "field \"image\" is required") {
t.Fatalf("\nExpected error\n %s\ngot\n %v",
"field \"image\" is required",
err,
)
}
}
func TestBadConfig(t *testing.T) {
path, err := filepath.Abs(filepath.Join("./test-fixtures", "bad-config.hcl"))
if err != nil {
t.Fatalf("Can't get absolute path for file: %s", err)
}
_, err = ParseFile(path)
if !strings.Contains(err.Error(), "seem to be of type boolean") {
t.Fatalf("\nExpected error\n %s\ngot\n %v",
"seem to be of type boolean",
err,
)
}
if !strings.Contains(err.Error(), "\"foo\" is an invalid field") {
t.Fatalf("\nExpected error\n %s\ngot\n %v",
"\"foo\" is an invalid field",
err,
)
}
}
func TestBadPorts(t *testing.T) {
path, err := filepath.Abs(filepath.Join("./test-fixtures", "bad-ports.hcl"))
if err != nil {
t.Fatalf("Can't get absolute path for file: %s", err)
}
_, err = ParseFile(path)
2015-11-15 08:10:48 +00:00
if !strings.Contains(err.Error(), errPortLabel.Error()) {
t.Fatalf("\nExpected error\n %s\ngot\n %v", errPortLabel, err)
}
}
func TestOverlappingPorts(t *testing.T) {
path, err := filepath.Abs(filepath.Join("./test-fixtures", "overlapping-ports.hcl"))
if err != nil {
2015-11-18 01:12:21 +00:00
t.Fatalf("Can't get absolute path for file: %s", err)
}
_, err = ParseFile(path)
if err == nil {
t.Fatalf("Expected an error")
}
2015-12-18 20:33:38 +00:00
if !strings.Contains(err.Error(), "found a port label collision") {
t.Fatalf("Expected collision error; got %v", err)
}
}
func TestIncompleteServiceDefn(t *testing.T) {
path, err := filepath.Abs(filepath.Join("./test-fixtures", "incorrect-service-def.hcl"))
if err != nil {
2015-11-18 01:12:21 +00:00
t.Fatalf("Can't get absolute path for file: %s", err)
}
_, err = ParseFile(path)
if err == nil {
t.Fatalf("Expected an error")
}
if !strings.Contains(err.Error(), "Only one service block may omit the Name field") {
t.Fatalf("Expected collision error; got %v", err)
}
}
2016-03-10 18:16:35 +00:00
func TestIncorrectKey(t *testing.T) {
path, err := filepath.Abs(filepath.Join("./test-fixtures", "basic_wrong_key.hcl"))
if err != nil {
t.Fatalf("Can't get absolute path for file: %s", err)
}
_, err = ParseFile(path)
if err == nil {
t.Fatalf("Expected an error")
}
if !strings.Contains(err.Error(), "* group: 'binsl', task: 'binstore', service: 'binstore-storagelocker-binsl-binstore', check -> invalid key: nterval") {
t.Fatalf("Expected collision error; got %v", err)
}
}