2017-02-01 00:43:57 +00:00
|
|
|
package consul_test
|
|
|
|
|
|
|
|
import (
|
2018-10-05 02:36:40 +00:00
|
|
|
"context"
|
2017-02-01 00:43:57 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
consulapi "github.com/hashicorp/consul/api"
|
|
|
|
"github.com/hashicorp/consul/testutil"
|
2018-10-05 02:36:40 +00:00
|
|
|
log "github.com/hashicorp/go-hclog"
|
2017-02-01 00:43:57 +00:00
|
|
|
"github.com/hashicorp/nomad/client/allocdir"
|
2018-06-12 00:03:40 +00:00
|
|
|
"github.com/hashicorp/nomad/client/allocrunner/taskrunner"
|
2017-02-01 00:43:57 +00:00
|
|
|
"github.com/hashicorp/nomad/client/config"
|
2018-11-16 23:29:59 +00:00
|
|
|
"github.com/hashicorp/nomad/client/devicemanager"
|
2018-11-28 03:42:22 +00:00
|
|
|
"github.com/hashicorp/nomad/client/pluginmanager/drivermanager"
|
2018-10-05 02:36:40 +00:00
|
|
|
"github.com/hashicorp/nomad/client/state"
|
2017-02-01 00:43:57 +00:00
|
|
|
"github.com/hashicorp/nomad/client/vaultclient"
|
|
|
|
"github.com/hashicorp/nomad/command/agent/consul"
|
2018-06-13 22:33:25 +00:00
|
|
|
"github.com/hashicorp/nomad/helper/testlog"
|
2017-02-01 00:43:57 +00:00
|
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
2018-10-05 02:36:40 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2017-02-01 00:43:57 +00:00
|
|
|
)
|
|
|
|
|
2018-10-05 02:36:40 +00:00
|
|
|
type mockUpdater struct {
|
|
|
|
logger log.Logger
|
|
|
|
}
|
|
|
|
|
2018-10-29 22:25:22 +00:00
|
|
|
func (m *mockUpdater) TaskStateUpdated() {
|
|
|
|
m.logger.Named("mock.updater").Debug("Update!")
|
2018-10-05 02:36:40 +00:00
|
|
|
}
|
|
|
|
|
2017-02-01 00:43:57 +00:00
|
|
|
// TestConsul_Integration asserts TaskRunner properly registers and deregisters
|
|
|
|
// services and checks with Consul using an embedded Consul agent.
|
|
|
|
func TestConsul_Integration(t *testing.T) {
|
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("-short set; skipping")
|
|
|
|
}
|
2018-10-05 02:36:40 +00:00
|
|
|
require := require.New(t)
|
2017-10-17 00:35:47 +00:00
|
|
|
|
2017-02-01 00:43:57 +00:00
|
|
|
// Create an embedded Consul server
|
2017-05-06 00:08:51 +00:00
|
|
|
testconsul, err := testutil.NewTestServerConfig(func(c *testutil.TestServerConfig) {
|
2017-02-01 00:43:57 +00:00
|
|
|
// If -v wasn't specified squelch consul logging
|
|
|
|
if !testing.Verbose() {
|
|
|
|
c.Stdout = ioutil.Discard
|
|
|
|
c.Stderr = ioutil.Discard
|
|
|
|
}
|
|
|
|
})
|
2017-05-06 00:08:51 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error starting test consul server: %v", err)
|
|
|
|
}
|
2017-02-01 00:43:57 +00:00
|
|
|
defer testconsul.Stop()
|
|
|
|
|
|
|
|
conf := config.DefaultConfig()
|
2017-05-03 22:14:19 +00:00
|
|
|
conf.Node = mock.Node()
|
2017-02-01 00:43:57 +00:00
|
|
|
conf.ConsulConfig.Addr = testconsul.HTTPAddr
|
|
|
|
consulConfig, err := conf.ConsulConfig.ApiConfig()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error generating consul config: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
conf.StateDir, err = ioutil.TempDir("", "nomadtest-consulstate")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error creating temp dir: %v", err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(conf.StateDir)
|
|
|
|
conf.AllocDir, err = ioutil.TempDir("", "nomdtest-consulalloc")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error creating temp dir: %v", err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(conf.AllocDir)
|
|
|
|
|
|
|
|
alloc := mock.Alloc()
|
|
|
|
task := alloc.Job.TaskGroups[0].Tasks[0]
|
|
|
|
task.Driver = "mock_driver"
|
|
|
|
task.Config = map[string]interface{}{
|
|
|
|
"run_for": "1h",
|
|
|
|
}
|
2017-12-19 00:18:42 +00:00
|
|
|
|
2017-02-01 00:43:57 +00:00
|
|
|
// Choose a port that shouldn't be in use
|
2017-12-19 00:18:42 +00:00
|
|
|
netResource := &structs.NetworkResource{
|
|
|
|
Device: "eth0",
|
|
|
|
IP: "127.0.0.1",
|
|
|
|
MBits: 50,
|
|
|
|
ReservedPorts: []structs.Port{{Label: "http", Value: 3}},
|
|
|
|
}
|
2018-10-03 16:47:18 +00:00
|
|
|
alloc.AllocatedResources.Tasks["web"].Networks[0] = netResource
|
|
|
|
|
2017-02-01 00:43:57 +00:00
|
|
|
task.Services = []*structs.Service{
|
|
|
|
{
|
|
|
|
Name: "httpd",
|
|
|
|
PortLabel: "http",
|
|
|
|
Tags: []string{"nomad", "test", "http"},
|
|
|
|
Checks: []*structs.ServiceCheck{
|
|
|
|
{
|
2017-12-19 00:18:42 +00:00
|
|
|
Name: "httpd-http-check",
|
|
|
|
Type: "http",
|
|
|
|
Path: "/",
|
|
|
|
Protocol: "http",
|
|
|
|
Interval: 9000 * time.Hour,
|
|
|
|
Timeout: 1, // fail as fast as possible
|
2017-02-01 00:43:57 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "httpd-script-check",
|
|
|
|
Type: "script",
|
|
|
|
Command: "/bin/true",
|
|
|
|
Interval: 10 * time.Second,
|
|
|
|
Timeout: 10 * time.Second,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "httpd2",
|
|
|
|
PortLabel: "http",
|
2017-12-08 01:08:25 +00:00
|
|
|
Tags: []string{
|
|
|
|
"test",
|
|
|
|
// Use URL-unfriendly tags to test #3620
|
|
|
|
"public-test.ettaviation.com:80/ redirect=302,https://test.ettaviation.com",
|
|
|
|
"public-test.ettaviation.com:443/",
|
|
|
|
},
|
2017-02-01 00:43:57 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-10-05 02:36:40 +00:00
|
|
|
logger := testlog.HCLogger(t)
|
|
|
|
logUpdate := &mockUpdater{logger}
|
2017-02-01 00:43:57 +00:00
|
|
|
allocDir := allocdir.NewAllocDir(logger, filepath.Join(conf.AllocDir, alloc.ID))
|
|
|
|
if err := allocDir.Build(); err != nil {
|
|
|
|
t.Fatalf("error building alloc dir: %v", err)
|
|
|
|
}
|
|
|
|
taskDir := allocDir.NewTaskDir(task.Name)
|
|
|
|
vclient := vaultclient.NewMockVaultClient()
|
|
|
|
consulClient, err := consulapi.NewClient(consulConfig)
|
2018-10-05 02:36:40 +00:00
|
|
|
require.Nil(err)
|
2017-10-17 00:35:47 +00:00
|
|
|
|
2018-09-13 17:43:40 +00:00
|
|
|
serviceClient := consul.NewServiceClient(consulClient.Agent(), testlog.HCLogger(t), true)
|
2017-02-01 00:43:57 +00:00
|
|
|
defer serviceClient.Shutdown() // just-in-case cleanup
|
|
|
|
consulRan := make(chan struct{})
|
|
|
|
go func() {
|
|
|
|
serviceClient.Run()
|
|
|
|
close(consulRan)
|
|
|
|
}()
|
2018-10-05 02:36:40 +00:00
|
|
|
|
|
|
|
// Build the config
|
|
|
|
config := &taskrunner.Config{
|
2018-11-28 03:42:22 +00:00
|
|
|
Alloc: alloc,
|
|
|
|
ClientConfig: conf,
|
|
|
|
Consul: serviceClient,
|
|
|
|
Task: task,
|
|
|
|
TaskDir: taskDir,
|
|
|
|
Logger: logger,
|
|
|
|
Vault: vclient,
|
|
|
|
StateDB: state.NoopDB{},
|
|
|
|
StateUpdater: logUpdate,
|
|
|
|
DeviceManager: devicemanager.NoopMockManager(),
|
|
|
|
DriverManager: drivermanager.TestDriverManager(t),
|
2018-10-05 02:36:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tr, err := taskrunner.NewTaskRunner(config)
|
|
|
|
require.NoError(err)
|
2017-02-01 00:43:57 +00:00
|
|
|
go tr.Run()
|
|
|
|
defer func() {
|
2017-04-12 20:35:14 +00:00
|
|
|
// Make sure we always shutdown task runner when the test exits
|
2017-02-01 00:43:57 +00:00
|
|
|
select {
|
|
|
|
case <-tr.WaitCh():
|
|
|
|
// Exited cleanly, no need to kill
|
|
|
|
default:
|
2018-10-05 02:36:40 +00:00
|
|
|
tr.Kill(context.Background(), &structs.TaskEvent{}) // just in case
|
2017-02-01 00:43:57 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Block waiting for the service to appear
|
|
|
|
catalog := consulClient.Catalog()
|
|
|
|
res, meta, err := catalog.Service("httpd2", "test", nil)
|
2018-10-05 02:36:40 +00:00
|
|
|
require.Nil(err)
|
2017-09-26 22:26:33 +00:00
|
|
|
|
2017-04-13 20:47:05 +00:00
|
|
|
for i := 0; len(res) == 0 && i < 10; i++ {
|
2017-02-01 00:43:57 +00:00
|
|
|
//Expected initial request to fail, do a blocking query
|
|
|
|
res, meta, err = catalog.Service("httpd2", "test", &consulapi.QueryOptions{WaitIndex: meta.LastIndex + 1, WaitTime: 3 * time.Second})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error querying for service: %v", err)
|
|
|
|
}
|
|
|
|
}
|
2018-10-05 02:36:40 +00:00
|
|
|
require.Len(res, 1)
|
2017-10-17 00:35:47 +00:00
|
|
|
|
|
|
|
// Truncate results
|
2017-02-01 00:43:57 +00:00
|
|
|
res = res[:]
|
|
|
|
|
|
|
|
// Assert the service with the checks exists
|
2017-04-13 20:47:05 +00:00
|
|
|
for i := 0; len(res) == 0 && i < 10; i++ {
|
2017-02-01 00:43:57 +00:00
|
|
|
res, meta, err = catalog.Service("httpd", "http", &consulapi.QueryOptions{WaitIndex: meta.LastIndex + 1, WaitTime: 3 * time.Second})
|
2018-10-05 02:36:40 +00:00
|
|
|
require.Nil(err)
|
2017-02-01 00:43:57 +00:00
|
|
|
}
|
2018-10-05 02:36:40 +00:00
|
|
|
require.Len(res, 1)
|
2017-02-01 00:43:57 +00:00
|
|
|
|
|
|
|
// Assert the script check passes (mock_driver script checks always
|
|
|
|
// pass) after having time to run once
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
checks, _, err := consulClient.Health().Checks("httpd", nil)
|
2018-10-05 02:36:40 +00:00
|
|
|
require.Nil(err)
|
|
|
|
require.Len(checks, 2)
|
2017-10-17 00:35:47 +00:00
|
|
|
|
2017-02-01 00:43:57 +00:00
|
|
|
for _, check := range checks {
|
|
|
|
if expected := "httpd"; check.ServiceName != expected {
|
|
|
|
t.Fatalf("expected checks to be for %q but found service name = %q", expected, check.ServiceName)
|
|
|
|
}
|
|
|
|
switch check.Name {
|
|
|
|
case "httpd-http-check":
|
|
|
|
// Port check should fail
|
|
|
|
if expected := consulapi.HealthCritical; check.Status != expected {
|
|
|
|
t.Errorf("expected %q status to be %q but found %q", check.Name, expected, check.Status)
|
|
|
|
}
|
|
|
|
case "httpd-script-check":
|
|
|
|
// mock_driver script checks always succeed
|
|
|
|
if expected := consulapi.HealthPassing; check.Status != expected {
|
|
|
|
t.Errorf("expected %q status to be %q but found %q", check.Name, expected, check.Status)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
t.Errorf("unexpected check %q with status %q", check.Name, check.Status)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-04 19:24:27 +00:00
|
|
|
// Assert the service client returns all the checks for the allocation.
|
2017-08-07 22:54:05 +00:00
|
|
|
reg, err := serviceClient.AllocRegistrations(alloc.ID)
|
2017-07-04 19:24:27 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error retrieving allocation checks: %v", err)
|
|
|
|
}
|
2017-08-07 22:54:05 +00:00
|
|
|
if reg == nil {
|
|
|
|
t.Fatalf("Unexpected nil allocation registration")
|
|
|
|
}
|
|
|
|
if snum := reg.NumServices(); snum != 2 {
|
|
|
|
t.Fatalf("Unexpected number of services registered. Got %d; want 2", snum)
|
|
|
|
}
|
|
|
|
if cnum := reg.NumChecks(); cnum != 2 {
|
|
|
|
t.Fatalf("Unexpected number of checks registered. Got %d; want 2", cnum)
|
2017-07-04 19:24:27 +00:00
|
|
|
}
|
|
|
|
|
2018-10-05 02:36:40 +00:00
|
|
|
logger.Debug("killing task")
|
2017-02-01 00:43:57 +00:00
|
|
|
|
|
|
|
// Kill the task
|
2018-10-05 02:36:40 +00:00
|
|
|
tr.Kill(context.Background(), &structs.TaskEvent{})
|
2017-02-01 00:43:57 +00:00
|
|
|
|
|
|
|
select {
|
|
|
|
case <-tr.WaitCh():
|
|
|
|
case <-time.After(10 * time.Second):
|
|
|
|
t.Fatalf("timed out waiting for Run() to exit")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shutdown Consul ServiceClient to ensure all pending operations complete
|
|
|
|
if err := serviceClient.Shutdown(); err != nil {
|
|
|
|
t.Errorf("error shutting down Consul ServiceClient: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure Consul is clean
|
|
|
|
services, _, err := catalog.Services(nil)
|
2018-10-05 02:36:40 +00:00
|
|
|
require.Nil(err)
|
|
|
|
require.Len(services, 1)
|
|
|
|
require.Contains(services, "consul")
|
2017-02-01 00:43:57 +00:00
|
|
|
}
|