Merge pull request #8977 from hashicorp/b-script-check-task
consul: fix validation of task in group-level script-checks
This commit is contained in:
commit
6dd862b927
|
@ -8,6 +8,7 @@ IMPROVEMENTS:
|
||||||
BUG FIXES:
|
BUG FIXES:
|
||||||
|
|
||||||
* core: Fixed a bug where blocking queries would not include the query's maximum wait time when calculating whether it was safe to retry. [[GH-8921](https://github.com/hashicorp/nomad/issues/8921)]
|
* core: Fixed a bug where blocking queries would not include the query's maximum wait time when calculating whether it was safe to retry. [[GH-8921](https://github.com/hashicorp/nomad/issues/8921)]
|
||||||
|
* consul: Fixed a bug to correctly validate task when using script-checks in group-level services [[GH-8952](https://github.com/hashicorp/nomad/issues/8952)]
|
||||||
|
|
||||||
## 0.12.5 (September 17, 2020)
|
## 0.12.5 (September 17, 2020)
|
||||||
|
|
||||||
|
|
|
@ -204,6 +204,10 @@ func (h *scriptCheckHook) newScriptChecks() map[string]*scriptCheck {
|
||||||
// for them. The group-level service and any check restart behaviors it
|
// for them. The group-level service and any check restart behaviors it
|
||||||
// needs are entirely encapsulated within the group service hook which
|
// needs are entirely encapsulated within the group service hook which
|
||||||
// watches Consul for status changes.
|
// watches Consul for status changes.
|
||||||
|
//
|
||||||
|
// The script check is associated with a group task if the service.task or
|
||||||
|
// service.check.task matches the task name. The service.check.task takes
|
||||||
|
// precedence.
|
||||||
tg := h.alloc.Job.LookupTaskGroup(h.alloc.TaskGroup)
|
tg := h.alloc.Job.LookupTaskGroup(h.alloc.TaskGroup)
|
||||||
interpolatedGroupServices := taskenv.InterpolateServices(h.taskEnv, tg.Services)
|
interpolatedGroupServices := taskenv.InterpolateServices(h.taskEnv, tg.Services)
|
||||||
for _, service := range interpolatedGroupServices {
|
for _, service := range interpolatedGroupServices {
|
||||||
|
@ -211,7 +215,7 @@ func (h *scriptCheckHook) newScriptChecks() map[string]*scriptCheck {
|
||||||
if check.Type != structs.ServiceCheckScript {
|
if check.Type != structs.ServiceCheckScript {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if check.TaskName != h.task.Name {
|
if !h.associated(h.task.Name, service.TaskName, check.TaskName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
groupTaskName := "group-" + tg.Name
|
groupTaskName := "group-" + tg.Name
|
||||||
|
@ -237,6 +241,20 @@ func (h *scriptCheckHook) newScriptChecks() map[string]*scriptCheck {
|
||||||
return scriptChecks
|
return scriptChecks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// associated returns true if the script check is associated with the task. This
|
||||||
|
// would be the case if the check.task is the same as task, or if the service.task
|
||||||
|
// is the same as the task _and_ check.task is not configured (i.e. the check
|
||||||
|
// inherits the task of the service).
|
||||||
|
func (*scriptCheckHook) associated(task, serviceTask, checkTask string) bool {
|
||||||
|
if checkTask == task {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if serviceTask == task && checkTask == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// heartbeater is the subset of consul agent functionality needed by script
|
// heartbeater is the subset of consul agent functionality needed by script
|
||||||
// checks to heartbeat
|
// checks to heartbeat
|
||||||
type heartbeater interface {
|
type heartbeater interface {
|
||||||
|
@ -371,7 +389,7 @@ const (
|
||||||
updateTTLBackoffLimit = 3 * time.Second
|
updateTTLBackoffLimit = 3 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// updateTTL updates the state to Consul, performing an expontential backoff
|
// updateTTL updates the state to Consul, performing an exponential backoff
|
||||||
// in the case where the check isn't registered in Consul to avoid a race between
|
// in the case where the check isn't registered in Consul to avoid a race between
|
||||||
// service registration and the first check.
|
// service registration and the first check.
|
||||||
func (s *scriptCheck) updateTTL(ctx context.Context, msg, state string) error {
|
func (s *scriptCheck) updateTTL(ctx context.Context, msg, state string) error {
|
||||||
|
|
|
@ -286,3 +286,27 @@ func TestScript_TaskEnvInterpolation(t *testing.T) {
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.Equal(t, "my-job-backend-check", check.check.Name)
|
require.Equal(t, "my-job-backend-check", check.check.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestScript_associated(t *testing.T) {
|
||||||
|
t.Run("neither set", func(t *testing.T) {
|
||||||
|
require.False(t, new(scriptCheckHook).associated("task1", "", ""))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("service set", func(t *testing.T) {
|
||||||
|
require.True(t, new(scriptCheckHook).associated("task1", "task1", ""))
|
||||||
|
require.False(t, new(scriptCheckHook).associated("task1", "task2", ""))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("check set", func(t *testing.T) {
|
||||||
|
require.True(t, new(scriptCheckHook).associated("task1", "", "task1"))
|
||||||
|
require.False(t, new(scriptCheckHook).associated("task1", "", "task2"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("both set", func(t *testing.T) {
|
||||||
|
// ensure check.task takes precedence over service.task
|
||||||
|
require.True(t, new(scriptCheckHook).associated("task1", "task1", "task1"))
|
||||||
|
require.False(t, new(scriptCheckHook).associated("task1", "task1", "task2"))
|
||||||
|
require.True(t, new(scriptCheckHook).associated("task1", "task2", "task1"))
|
||||||
|
require.False(t, new(scriptCheckHook).associated("task1", "task2", "task2"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -478,6 +478,7 @@ func TestJobEndpoint_Register_ConnectExposeCheck(t *testing.T) {
|
||||||
Name: "check2",
|
Name: "check2",
|
||||||
Type: "script",
|
Type: "script",
|
||||||
Command: "/bin/true",
|
Command: "/bin/true",
|
||||||
|
TaskName: "web",
|
||||||
Interval: 1 * time.Second,
|
Interval: 1 * time.Second,
|
||||||
Timeout: 1 * time.Second,
|
Timeout: 1 * time.Second,
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -5801,6 +5801,12 @@ func (tg *TaskGroup) Validate(j *Job) error {
|
||||||
mErr.Errors = append(mErr.Errors, outer)
|
mErr.Errors = append(mErr.Errors, outer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate group service script-checks
|
||||||
|
if err := tg.validateScriptChecksInGroupServices(); err != nil {
|
||||||
|
outer := fmt.Errorf("Task group service check validation failed: %v", err)
|
||||||
|
mErr.Errors = append(mErr.Errors, outer)
|
||||||
|
}
|
||||||
|
|
||||||
// Validate the scaling policy
|
// Validate the scaling policy
|
||||||
if err := tg.validateScalingPolicy(j); err != nil {
|
if err := tg.validateScalingPolicy(j); err != nil {
|
||||||
outer := fmt.Errorf("Task group scaling policy validation failed: %v", err)
|
outer := fmt.Errorf("Task group scaling policy validation failed: %v", err)
|
||||||
|
@ -5950,6 +5956,26 @@ func (tg *TaskGroup) validateServices() error {
|
||||||
return mErr.ErrorOrNil()
|
return mErr.ErrorOrNil()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateScriptChecksInGroupServices ensures group-level services with script
|
||||||
|
// checks know what task driver to use. Either the service.task or service.check.task
|
||||||
|
// parameter must be configured.
|
||||||
|
func (tg *TaskGroup) validateScriptChecksInGroupServices() error {
|
||||||
|
var mErr multierror.Error
|
||||||
|
for _, service := range tg.Services {
|
||||||
|
if service.TaskName == "" {
|
||||||
|
for _, check := range service.Checks {
|
||||||
|
if check.Type == "script" && check.TaskName == "" {
|
||||||
|
mErr.Errors = append(mErr.Errors,
|
||||||
|
fmt.Errorf("Service [%s]->%s or Check %s must specify task parameter",
|
||||||
|
tg.Name, service.Name, check.Name,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mErr.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
// validateScalingPolicy ensures that the scaling policy has consistent
|
// validateScalingPolicy ensures that the scaling policy has consistent
|
||||||
// min and max, not in conflict with the task group count
|
// min and max, not in conflict with the task group count
|
||||||
func (tg *TaskGroup) validateScalingPolicy(j *Job) error {
|
func (tg *TaskGroup) validateScalingPolicy(j *Job) error {
|
||||||
|
|
|
@ -5584,3 +5584,69 @@ func TestAllocatedSharedResources_Canonicalize(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, a.Ports)
|
}, a.Ports)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTaskGroup_validateScriptChecksInGroupServices(t *testing.T) {
|
||||||
|
t.Run("service task not set", func(t *testing.T) {
|
||||||
|
tg := &TaskGroup{
|
||||||
|
Name: "group1",
|
||||||
|
Services: []*Service{{
|
||||||
|
Name: "service1",
|
||||||
|
TaskName: "", // unset
|
||||||
|
Checks: []*ServiceCheck{{
|
||||||
|
Name: "check1",
|
||||||
|
Type: "script",
|
||||||
|
TaskName: "", // unset
|
||||||
|
}, {
|
||||||
|
Name: "check2",
|
||||||
|
Type: "ttl", // not script
|
||||||
|
}, {
|
||||||
|
Name: "check3",
|
||||||
|
Type: "script",
|
||||||
|
TaskName: "", // unset
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
Name: "service2",
|
||||||
|
Checks: []*ServiceCheck{{
|
||||||
|
Type: "script",
|
||||||
|
TaskName: "task1", // set
|
||||||
|
}},
|
||||||
|
}, {
|
||||||
|
Name: "service3",
|
||||||
|
TaskName: "", // unset
|
||||||
|
Checks: []*ServiceCheck{{
|
||||||
|
Name: "check1",
|
||||||
|
Type: "script",
|
||||||
|
TaskName: "", // unset
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
errStr := tg.validateScriptChecksInGroupServices().Error()
|
||||||
|
require.Contains(t, errStr, "Service [group1]->service1 or Check check1 must specify task parameter")
|
||||||
|
require.Contains(t, errStr, "Service [group1]->service1 or Check check3 must specify task parameter")
|
||||||
|
require.Contains(t, errStr, "Service [group1]->service3 or Check check1 must specify task parameter")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("service task set", func(t *testing.T) {
|
||||||
|
tgOK := &TaskGroup{
|
||||||
|
Name: "group1",
|
||||||
|
Services: []*Service{{
|
||||||
|
Name: "service1",
|
||||||
|
TaskName: "task1",
|
||||||
|
Checks: []*ServiceCheck{{
|
||||||
|
Name: "check1",
|
||||||
|
Type: "script",
|
||||||
|
}, {
|
||||||
|
Name: "check2",
|
||||||
|
Type: "ttl",
|
||||||
|
}, {
|
||||||
|
Name: "check3",
|
||||||
|
Type: "script",
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
mErrOK := tgOK.validateScriptChecksInGroupServices()
|
||||||
|
require.Nil(t, mErrOK)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -246,7 +246,8 @@ scripts.
|
||||||
- `task` `(string: <required>)` - Specifies the task associated with this
|
- `task` `(string: <required>)` - Specifies the task associated with this
|
||||||
check. Scripts are executed within the task's environment, and
|
check. Scripts are executed within the task's environment, and
|
||||||
`check_restart` stanzas will apply to the specified task. For `checks` on group
|
`check_restart` stanzas will apply to the specified task. For `checks` on group
|
||||||
level `services` only.
|
level `services` only. Inherits the [`service.task`][service_task] value if not
|
||||||
|
set.
|
||||||
|
|
||||||
- `timeout` `(string: <required>)` - Specifies how long Consul will wait for a
|
- `timeout` `(string: <required>)` - Specifies how long Consul will wait for a
|
||||||
health check query to succeed. This is specified using a label suffix like
|
health check query to succeed. This is specified using a label suffix like
|
||||||
|
@ -714,3 +715,4 @@ advertise and check directly since Nomad isn't managing any port assignments.
|
||||||
[shutdowndelay]: /docs/job-specification/task#shutdown_delay
|
[shutdowndelay]: /docs/job-specification/task#shutdown_delay
|
||||||
[killsignal]: /docs/job-specification/task#kill_signal
|
[killsignal]: /docs/job-specification/task#kill_signal
|
||||||
[killtimeout]: /docs/job-specification/task#kill_timeout
|
[killtimeout]: /docs/job-specification/task#kill_timeout
|
||||||
|
[service_task]: /docs/job-specification/service#task-1
|
||||||
|
|
Loading…
Reference in a new issue