0f29dcc935
In Nomad prior to Consul Connect, all Consul checks work the same except for Script checks. Because the Task being checked is running in its own container namespaces, the check is executed by Nomad in the Task's context. If the Script check passes, Nomad uses the TTL check feature of Consul to update the check status. This means in order to run a Script check, we need to know what Task to execute it in. To support Consul Connect, we need Group Services, and these need to be registered in Consul along with their checks. We could push the Service down into the Task, but this doesn't work if someone wants to associate a service with a task's ports, but do script checks in another task in the allocation. Because Nomad is handling the Script check and not Consul anyways, this moves the script check handling into the task runner so that the task runner can own the script check's configuration and lifecycle. This will allow us to pass the group service check configuration down into a task without associating the service itself with the task. When tasks are checked for script checks, we walk back through their task group to see if there are script checks associated with the task. If so, we'll spin off script check tasklets for them. The group-level service and any restart behaviors it needs are entirely encapsulated within the group service hook.
140 lines
4.2 KiB
Go
140 lines
4.2 KiB
Go
package consul
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
|
|
log "github.com/hashicorp/go-hclog"
|
|
|
|
"github.com/hashicorp/nomad/command/agent/consul"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
testing "github.com/mitchellh/go-testing-interface"
|
|
)
|
|
|
|
// MockConsulOp represents the register/deregister operations.
|
|
type MockConsulOp struct {
|
|
Op string // add, remove, or update
|
|
AllocID string
|
|
Name string // task or group name
|
|
}
|
|
|
|
func NewMockConsulOp(op, allocID, name string) MockConsulOp {
|
|
switch op {
|
|
case "add", "remove", "update", "alloc_registrations",
|
|
"add_group", "remove_group", "update_group", "update_ttl":
|
|
default:
|
|
panic(fmt.Errorf("invalid consul op: %s", op))
|
|
}
|
|
return MockConsulOp{
|
|
Op: op,
|
|
AllocID: allocID,
|
|
Name: name,
|
|
}
|
|
}
|
|
|
|
// MockConsulServiceClient implements the ConsulServiceAPI interface to record
|
|
// and log task registration/deregistration.
|
|
type MockConsulServiceClient struct {
|
|
ops []MockConsulOp
|
|
mu sync.Mutex
|
|
|
|
logger log.Logger
|
|
|
|
// AllocRegistrationsFn allows injecting return values for the
|
|
// AllocRegistrations function.
|
|
AllocRegistrationsFn func(allocID string) (*consul.AllocRegistration, error)
|
|
}
|
|
|
|
func NewMockConsulServiceClient(t testing.T, logger log.Logger) *MockConsulServiceClient {
|
|
logger = logger.Named("mock_consul")
|
|
m := MockConsulServiceClient{
|
|
ops: make([]MockConsulOp, 0, 20),
|
|
logger: logger,
|
|
}
|
|
return &m
|
|
}
|
|
|
|
func (m *MockConsulServiceClient) RegisterGroup(alloc *structs.Allocation) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
tg := alloc.Job.LookupTaskGroup(alloc.TaskGroup)
|
|
m.logger.Trace("RegisterGroup", "alloc_id", alloc.ID, "num_services", len(tg.Services))
|
|
m.ops = append(m.ops, NewMockConsulOp("add_group", alloc.ID, alloc.TaskGroup))
|
|
return nil
|
|
}
|
|
|
|
func (m *MockConsulServiceClient) UpdateGroup(_, alloc *structs.Allocation) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
tg := alloc.Job.LookupTaskGroup(alloc.TaskGroup)
|
|
m.logger.Trace("UpdateGroup", "alloc_id", alloc.ID, "num_services", len(tg.Services))
|
|
m.ops = append(m.ops, NewMockConsulOp("update_group", alloc.ID, alloc.TaskGroup))
|
|
return nil
|
|
}
|
|
|
|
func (m *MockConsulServiceClient) RemoveGroup(alloc *structs.Allocation) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
tg := alloc.Job.LookupTaskGroup(alloc.TaskGroup)
|
|
m.logger.Trace("RemoveGroup", "alloc_id", alloc.ID, "num_services", len(tg.Services))
|
|
m.ops = append(m.ops, NewMockConsulOp("remove_group", alloc.ID, alloc.TaskGroup))
|
|
return nil
|
|
}
|
|
|
|
func (m *MockConsulServiceClient) UpdateTask(old, newSvcs *consul.TaskServices) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.logger.Trace("UpdateTask", "alloc_id", newSvcs.AllocID, "task", newSvcs.Name,
|
|
"old_services", len(old.Services), "new_services", len(newSvcs.Services),
|
|
)
|
|
m.ops = append(m.ops, NewMockConsulOp("update", newSvcs.AllocID, newSvcs.Name))
|
|
return nil
|
|
}
|
|
|
|
func (m *MockConsulServiceClient) RegisterTask(task *consul.TaskServices) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.logger.Trace("RegisterTask", "alloc_id", task.AllocID, "task", task.Name,
|
|
"services", len(task.Services),
|
|
)
|
|
m.ops = append(m.ops, NewMockConsulOp("add", task.AllocID, task.Name))
|
|
return nil
|
|
}
|
|
|
|
func (m *MockConsulServiceClient) RemoveTask(task *consul.TaskServices) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.logger.Trace("RemoveTask", "alloc_id", task.AllocID, "task", task.Name,
|
|
"services", len(task.Services),
|
|
)
|
|
m.ops = append(m.ops, NewMockConsulOp("remove", task.AllocID, task.Name))
|
|
}
|
|
|
|
func (m *MockConsulServiceClient) AllocRegistrations(allocID string) (*consul.AllocRegistration, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.logger.Trace("AllocRegistrations", "alloc_id", allocID)
|
|
m.ops = append(m.ops, NewMockConsulOp("alloc_registrations", allocID, ""))
|
|
|
|
if m.AllocRegistrationsFn != nil {
|
|
return m.AllocRegistrationsFn(allocID)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockConsulServiceClient) UpdateTTL(checkID, output, status string) error {
|
|
// TODO(tgross): this method is here so we can implement the
|
|
// interface but the locking we need for testing creates a lot
|
|
// of opportunities for deadlocks in testing that will never
|
|
// appear in live code.
|
|
m.logger.Trace("UpdateTTL", "check_id", checkID, "status", status)
|
|
return nil
|
|
}
|
|
|
|
func (m *MockConsulServiceClient) GetOps() []MockConsulOp {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.ops
|
|
}
|