diff --git a/def/task.go b/def/task.go
index 015171e..829a4b7 100644
--- a/def/task.go
+++ b/def/task.go
@@ -20,6 +20,7 @@ package def
import (
"encoding/json"
+ "github.com/spdfg/elektron/utilities/validation"
"os"
mesos "github.com/mesos/mesos-go/api/v0/mesosproto"
@@ -55,6 +56,18 @@ func TasksFromJSON(uri string) ([]Task, error) {
return nil, errors.Wrap(err, "Error unmarshalling")
}
+ // Validating task definitions.
+ for _, task := range tasks {
+ err := validation.Validate("invalid task definition",
+ ValidatorForTask(task,
+ withNameValidator(),
+ withImageValidator(),
+ withResourceValidator()))
+ if err != nil {
+ return tasks, err
+ }
+ }
+
initTaskResourceRequirements(tasks)
return tasks, nil
}
diff --git a/def/taskValidators.go b/def/taskValidators.go
new file mode 100644
index 0000000..535ea30
--- /dev/null
+++ b/def/taskValidators.go
@@ -0,0 +1,91 @@
+// Copyright (C) 2018 spdfg
+//
+// This file is part of Elektron.
+//
+// Elektron is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Elektron is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Elektron. If not, see .
+//
+
+package def
+
+import (
+ "github.com/pkg/errors"
+ "github.com/spdfg/elektron/utilities/validation"
+ "regexp"
+)
+
+// taskValidator is a validator that validates one or more attributes of a task.
+type taskValidator func(Task) error
+
+// ValidatorForTask returns a validator that runs all provided taskValidators and
+// returns an error corresponding to the first taskValidator that failed.
+func ValidatorForTask(t Task, taskValidators ...taskValidator) validation.Validator {
+ return func() error {
+ for _, tv := range taskValidators {
+ if err := tv(t); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ }
+}
+
+// withNameValidator returns a taskValidator that checks whether the task name is valid.
+func withNameValidator() taskValidator {
+ return func(t Task) error {
+ // Task name cannot be empty string.
+ if t.Name == "" {
+ return errors.New("task name cannot be empty string")
+ }
+
+ // Task name cannot contain tabs or spaces.
+ matched, _ := regexp.MatchString("\\t+|\\s+", t.Name)
+ if matched {
+ return errors.New("task name cannot contain tabs or spaces")
+ }
+
+ return nil
+ }
+}
+
+// withResourceValidator returns a taskValidator that checks whether the resource requirements are valid.
+// Currently, only requirements for traditional resources (cpu and memory) are validated.
+func withResourceValidator() taskValidator {
+ return func(t Task) error {
+ // CPU value cannot be 0.
+ if t.CPU == 0.0 {
+ return errors.New("cpu resource for task cannot be 0")
+ }
+
+ // RAM value cannot be 0.
+ if t.RAM == 0.0 {
+ return errors.New("memory resource for task cannot be 0")
+ }
+
+ return nil
+ }
+}
+
+// withImageValidator returns a taskValidator that checks whether a valid image has been
+// provided for the task.
+func withImageValidator() taskValidator {
+ return func(t Task) error {
+ // Image cannot be empty.
+ if t.Image == "" {
+ return errors.New("valid image needs to be provided for task")
+ }
+
+ return nil
+ }
+}
diff --git a/def/taskValidators_test.go b/def/taskValidators_test.go
new file mode 100644
index 0000000..ecb338e
--- /dev/null
+++ b/def/taskValidators_test.go
@@ -0,0 +1,75 @@
+// Copyright (C) 2018 spdfg
+//
+// This file is part of Elektron.
+//
+// Elektron is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Elektron is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Elektron. If not, see .
+//
+
+package def
+
+import (
+ "github.com/spdfg/elektron/utilities/validation"
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestValidatorForTask(t *testing.T) {
+ getValidator := func(t Task) validation.Validator {
+ return ValidatorForTask(t,
+ withNameValidator(),
+ withImageValidator(),
+ withResourceValidator())
+ }
+
+ test := func(task Task, expectErr bool, message string) {
+ validator := getValidator(task)
+ if expectErr {
+ assert.Error(t, validation.Validate(message, validator))
+ } else {
+ assert.NoError(t, validation.Validate(message, validator))
+ }
+ }
+
+ inst := 10
+ validTask := Task{
+ Name: "minife",
+ CPU: 3.0,
+ RAM: 4096,
+ Watts: 50,
+ Image: "rdelvalle/minife:electron1",
+ CMD: "cd src && mpirun -np 1 miniFE.x -nx 100 -ny 100 -nz 100",
+ Instances: &inst,
+ }
+ test(validTask, false, "invalid task definition")
+ // Task with empty name.
+ invalidTaskEmptyName := validTask
+ invalidTaskEmptyName.Name = ""
+ test(invalidTaskEmptyName, true, "invalid task definition")
+ // Task with name that contains spaces.
+ invalidTaskNameWithSpaces := validTask
+ invalidTaskNameWithSpaces.Name = "my task"
+ test(invalidTaskNameWithSpaces, true, "invalid task definition")
+ // Task with invalid image.
+ invalidTaskImage := validTask
+ invalidTaskImage.Image = ""
+ test(invalidTaskImage, true, "invalid task definition")
+ // Task with invalid cpu resources.
+ invalidTaskResourcesCPU := validTask
+ invalidTaskResourcesCPU.CPU = 0
+ test(invalidTaskResourcesCPU, true, "invalid task definition")
+ // Task with invalid memory resources.
+ invalidTaskResourcesRAM := validTask
+ invalidTaskResourcesRAM.RAM = 0
+ test(invalidTaskResourcesRAM, true, "invalid task definition")
+}
diff --git a/scheduler.go b/scheduler.go
index 98b62e7..67693bb 100644
--- a/scheduler.go
+++ b/scheduler.go
@@ -208,7 +208,7 @@ func main() {
}
tasks, err := def.TasksFromJSON(*tasksFile)
if err != nil || len(tasks) == 0 {
- log.Fatal("Invalid tasks specification file provided.")
+ log.Fatal(err)
}
schedOptions = append(schedOptions, schedulers.WithTasks(tasks))
diff --git a/utilities/validation/validate.go b/utilities/validation/validate.go
new file mode 100644
index 0000000..265d91b
--- /dev/null
+++ b/utilities/validation/validate.go
@@ -0,0 +1,40 @@
+// Copyright (C) 2018 spdfg
+//
+// This file is part of Elektron.
+//
+// Elektron is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Elektron is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Elektron. If not, see .
+//
+
+// Package validation contains utilities to help run validators.
+package validation
+
+import "github.com/pkg/errors"
+
+// Validator is a function that performs some sort of validation.
+// To keep things generic, this function does not accept any arguments.
+// In practice, a validator could be a closure.
+type Validator func() error
+
+// Validate a list of validators.
+// If validation fails, then wrap the returned error with the given base
+// error message.
+func Validate(baseErrMsg string, validators ...Validator) error {
+ for _, v := range validators {
+ if err := v(); err != nil {
+ return errors.Wrap(err, baseErrMsg)
+ }
+ }
+
+ return nil
+}