2015-08-30 23:35:04 +00:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
2016-02-04 03:58:39 +00:00
|
|
|
"fmt"
|
2015-09-24 21:29:53 +00:00
|
|
|
"os"
|
2015-08-30 23:35:04 +00:00
|
|
|
"testing"
|
2015-08-31 00:10:17 +00:00
|
|
|
"time"
|
2015-08-30 23:35:04 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
2015-08-31 00:10:17 +00:00
|
|
|
"github.com/hashicorp/nomad/testutil"
|
2015-09-23 00:10:03 +00:00
|
|
|
|
2015-09-23 01:48:42 +00:00
|
|
|
ctestutil "github.com/hashicorp/nomad/client/testutil"
|
2015-08-30 23:35:04 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type MockAllocStateUpdater struct {
|
|
|
|
Count int
|
|
|
|
Allocs []*structs.Allocation
|
|
|
|
Err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *MockAllocStateUpdater) Update(alloc *structs.Allocation) error {
|
|
|
|
m.Count += 1
|
|
|
|
m.Allocs = append(m.Allocs, alloc)
|
|
|
|
return m.Err
|
|
|
|
}
|
|
|
|
|
2015-11-14 06:07:13 +00:00
|
|
|
func testAllocRunner(restarts bool) (*MockAllocStateUpdater, *AllocRunner) {
|
2015-08-30 23:35:04 +00:00
|
|
|
logger := testLogger()
|
|
|
|
conf := DefaultConfig()
|
2015-09-24 21:29:53 +00:00
|
|
|
conf.StateDir = os.TempDir()
|
|
|
|
conf.AllocDir = os.TempDir()
|
2015-08-30 23:35:04 +00:00
|
|
|
upd := &MockAllocStateUpdater{}
|
|
|
|
alloc := mock.Alloc()
|
2015-12-11 19:02:23 +00:00
|
|
|
consulClient, _ := NewConsulService(&consulServiceConfig{logger, "127.0.0.1:8500", "", "", false, false, &structs.Node{}})
|
2015-11-14 06:07:13 +00:00
|
|
|
if !restarts {
|
2016-02-02 23:08:07 +00:00
|
|
|
*alloc.Job.LookupTaskGroup(alloc.TaskGroup).RestartPolicy = structs.RestartPolicy{Attempts: 0}
|
2016-02-02 23:35:25 +00:00
|
|
|
alloc.Job.Type = structs.JobTypeBatch
|
2015-11-14 06:07:13 +00:00
|
|
|
}
|
|
|
|
|
2015-11-18 08:50:45 +00:00
|
|
|
ar := NewAllocRunner(logger, conf, upd.Update, alloc, consulClient)
|
2015-08-30 23:35:04 +00:00
|
|
|
return upd, ar
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAllocRunner_SimpleRun(t *testing.T) {
|
2015-09-23 01:48:42 +00:00
|
|
|
ctestutil.ExecCompatible(t)
|
2015-11-14 06:07:13 +00:00
|
|
|
upd, ar := testAllocRunner(false)
|
2015-08-31 00:10:17 +00:00
|
|
|
go ar.Run()
|
|
|
|
defer ar.Destroy()
|
2015-08-30 23:35:04 +00:00
|
|
|
|
2015-08-31 00:10:17 +00:00
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
|
|
if upd.Count == 0 {
|
2016-02-04 03:58:39 +00:00
|
|
|
return false, fmt.Errorf("No updates")
|
2015-08-31 00:10:17 +00:00
|
|
|
}
|
|
|
|
last := upd.Allocs[upd.Count-1]
|
2016-02-04 03:58:39 +00:00
|
|
|
if last.ClientStatus == structs.AllocClientStatusDead {
|
|
|
|
return false, fmt.Errorf("got status %v; want %v", last.ClientStatus, structs.AllocClientStatusDead)
|
|
|
|
}
|
|
|
|
return true, nil
|
2015-08-31 00:10:17 +00:00
|
|
|
}, func(err error) {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
})
|
2015-08-30 23:35:04 +00:00
|
|
|
}
|
|
|
|
|
2016-02-04 21:09:53 +00:00
|
|
|
func TestAllocRunner_TerminalUpdate_Destroy(t *testing.T) {
|
|
|
|
ctestutil.ExecCompatible(t)
|
|
|
|
upd, ar := testAllocRunner(false)
|
|
|
|
|
|
|
|
// Ensure task takes some time
|
|
|
|
task := ar.alloc.Job.TaskGroups[0].Tasks[0]
|
|
|
|
task.Config["command"] = "/bin/sleep"
|
|
|
|
task.Config["args"] = []string{"10"}
|
|
|
|
go ar.Run()
|
|
|
|
|
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
|
|
if upd.Count == 0 {
|
|
|
|
return false, fmt.Errorf("No updates")
|
|
|
|
}
|
|
|
|
last := upd.Allocs[upd.Count-1]
|
|
|
|
if last.ClientStatus == structs.AllocClientStatusRunning {
|
|
|
|
return false, fmt.Errorf("got status %v; want %v", last.ClientStatus, structs.AllocClientStatusRunning)
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}, func(err error) {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Update the alloc to be terminal which should cause the alloc runner to
|
|
|
|
// stop the tasks and wait for a destroy.
|
|
|
|
update := ar.alloc.Copy()
|
|
|
|
update.DesiredStatus = structs.AllocDesiredStatusStop
|
|
|
|
ar.Update(update)
|
|
|
|
|
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
|
|
if upd.Count == 0 {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the status has changed.
|
|
|
|
last := upd.Allocs[upd.Count-1]
|
|
|
|
if last.ClientStatus != structs.AllocClientStatusDead {
|
|
|
|
return false, fmt.Errorf("got client status %v; want %v", last.ClientStatus, structs.AllocClientStatusDead)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the state still exists
|
|
|
|
if _, err := os.Stat(ar.stateFilePath()); err != nil {
|
|
|
|
return false, fmt.Errorf("state file destroyed: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the alloc directory still exists
|
|
|
|
if _, err := os.Stat(ar.ctx.AllocDir.AllocDir); err != nil {
|
|
|
|
return false, fmt.Errorf("alloc dir destroyed: %v", ar.ctx.AllocDir.AllocDir)
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}, func(err error) {
|
2016-02-04 22:19:27 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
2016-02-04 21:09:53 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
// Send the destroy signal and ensure the AllocRunner cleans up.
|
|
|
|
ar.Destroy()
|
|
|
|
|
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
|
|
if upd.Count == 0 {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the status has changed.
|
|
|
|
last := upd.Allocs[upd.Count-1]
|
|
|
|
if last.ClientStatus != structs.AllocClientStatusDead {
|
|
|
|
return false, fmt.Errorf("got client status %v; want %v", last.ClientStatus, structs.AllocClientStatusDead)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the state was cleaned
|
|
|
|
if _, err := os.Stat(ar.stateFilePath()); err == nil {
|
|
|
|
return false, fmt.Errorf("state file still exists: %v", ar.stateFilePath())
|
|
|
|
} else if !os.IsNotExist(err) {
|
|
|
|
return false, fmt.Errorf("stat err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the alloc directory was cleaned
|
|
|
|
if _, err := os.Stat(ar.ctx.AllocDir.AllocDir); err == nil {
|
|
|
|
return false, fmt.Errorf("alloc dir still exists: %v", ar.ctx.AllocDir.AllocDir)
|
|
|
|
} else if !os.IsNotExist(err) {
|
|
|
|
return false, fmt.Errorf("stat err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}, func(err error) {
|
2016-02-04 22:19:27 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
2016-02-04 21:09:53 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-08-30 23:35:04 +00:00
|
|
|
func TestAllocRunner_Destroy(t *testing.T) {
|
2015-09-23 01:48:42 +00:00
|
|
|
ctestutil.ExecCompatible(t)
|
2015-11-14 06:07:13 +00:00
|
|
|
upd, ar := testAllocRunner(false)
|
2015-08-31 00:10:17 +00:00
|
|
|
|
|
|
|
// Ensure task takes some time
|
|
|
|
task := ar.alloc.Job.TaskGroups[0].Tasks[0]
|
|
|
|
task.Config["command"] = "/bin/sleep"
|
2015-11-18 23:16:42 +00:00
|
|
|
task.Config["args"] = []string{"10"}
|
2015-08-31 00:10:17 +00:00
|
|
|
go ar.Run()
|
|
|
|
start := time.Now()
|
|
|
|
|
|
|
|
// Begin the tear down
|
|
|
|
go func() {
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
ar.Destroy()
|
|
|
|
}()
|
|
|
|
|
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
|
|
if upd.Count == 0 {
|
|
|
|
return false, nil
|
|
|
|
}
|
2016-02-04 21:09:53 +00:00
|
|
|
|
|
|
|
// Check the status has changed.
|
2015-08-31 00:10:17 +00:00
|
|
|
last := upd.Allocs[upd.Count-1]
|
2016-02-04 21:09:53 +00:00
|
|
|
if last.ClientStatus != structs.AllocClientStatusDead {
|
|
|
|
return false, fmt.Errorf("got client status %v; want %v", last.ClientStatus, structs.AllocClientStatusDead)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the state was cleaned
|
|
|
|
if _, err := os.Stat(ar.stateFilePath()); err == nil {
|
|
|
|
return false, fmt.Errorf("state file still exists: %v", ar.stateFilePath())
|
|
|
|
} else if !os.IsNotExist(err) {
|
|
|
|
return false, fmt.Errorf("stat err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the alloc directory was cleaned
|
|
|
|
if _, err := os.Stat(ar.ctx.AllocDir.AllocDir); err == nil {
|
|
|
|
return false, fmt.Errorf("alloc dir still exists: %v", ar.ctx.AllocDir.AllocDir)
|
|
|
|
} else if !os.IsNotExist(err) {
|
|
|
|
return false, fmt.Errorf("stat err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
2015-08-31 00:10:17 +00:00
|
|
|
}, func(err error) {
|
2016-02-04 22:19:27 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
2015-08-31 00:10:17 +00:00
|
|
|
})
|
|
|
|
|
2016-01-20 20:00:20 +00:00
|
|
|
if time.Since(start) > 15*time.Second {
|
2015-08-31 00:10:17 +00:00
|
|
|
t.Fatalf("took too long to terminate")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAllocRunner_Update(t *testing.T) {
|
2015-09-23 01:48:42 +00:00
|
|
|
ctestutil.ExecCompatible(t)
|
2016-02-01 21:57:35 +00:00
|
|
|
_, ar := testAllocRunner(false)
|
2015-08-31 00:10:17 +00:00
|
|
|
|
|
|
|
// Ensure task takes some time
|
|
|
|
task := ar.alloc.Job.TaskGroups[0].Tasks[0]
|
|
|
|
task.Config["command"] = "/bin/sleep"
|
2015-11-18 23:16:42 +00:00
|
|
|
task.Config["args"] = []string{"10"}
|
2015-08-31 00:10:17 +00:00
|
|
|
go ar.Run()
|
|
|
|
defer ar.Destroy()
|
|
|
|
|
|
|
|
// Update the alloc definition
|
|
|
|
newAlloc := new(structs.Allocation)
|
|
|
|
*newAlloc = *ar.alloc
|
2016-02-01 21:57:35 +00:00
|
|
|
newAlloc.Name = "FOO"
|
|
|
|
newAlloc.AllocModifyIndex++
|
2015-08-31 00:10:17 +00:00
|
|
|
ar.Update(newAlloc)
|
|
|
|
|
2016-02-01 21:57:35 +00:00
|
|
|
// Check the alloc runner stores the update allocation.
|
2015-08-31 00:10:17 +00:00
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
2016-02-01 21:57:35 +00:00
|
|
|
return ar.Alloc().Name == "FOO", nil
|
2015-08-31 00:10:17 +00:00
|
|
|
}, func(err error) {
|
2016-02-01 21:57:35 +00:00
|
|
|
t.Fatalf("err: %v %#v", err, ar.Alloc())
|
2015-08-31 00:10:17 +00:00
|
|
|
})
|
2015-08-30 23:35:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestAllocRunner_SaveRestoreState(t *testing.T) {
|
2015-11-11 00:53:59 +00:00
|
|
|
ctestutil.ExecCompatible(t)
|
2015-11-14 06:07:13 +00:00
|
|
|
upd, ar := testAllocRunner(false)
|
2015-08-31 00:10:17 +00:00
|
|
|
|
|
|
|
// Ensure task takes some time
|
|
|
|
task := ar.alloc.Job.TaskGroups[0].Tasks[0]
|
|
|
|
task.Config["command"] = "/bin/sleep"
|
2015-11-18 23:16:42 +00:00
|
|
|
task.Config["args"] = []string{"10"}
|
2015-08-31 00:10:17 +00:00
|
|
|
go ar.Run()
|
|
|
|
|
|
|
|
// Snapshot state
|
2016-01-21 22:52:41 +00:00
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
|
|
return len(ar.tasks) == 1, nil
|
|
|
|
}, func(err error) {
|
|
|
|
t.Fatalf("task never started: %v", err)
|
|
|
|
})
|
|
|
|
|
2015-08-31 00:10:17 +00:00
|
|
|
err := ar.SaveState()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new alloc runner
|
2015-12-11 19:02:23 +00:00
|
|
|
consulClient, err := NewConsulService(&consulServiceConfig{ar.logger, "127.0.0.1:8500", "", "", false, false, &structs.Node{}})
|
2015-08-31 00:10:17 +00:00
|
|
|
ar2 := NewAllocRunner(ar.logger, ar.config, upd.Update,
|
2015-11-18 08:50:45 +00:00
|
|
|
&structs.Allocation{ID: ar.alloc.ID}, consulClient)
|
2015-08-31 00:10:17 +00:00
|
|
|
err = ar2.RestoreState()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
go ar2.Run()
|
|
|
|
|
|
|
|
// Destroy and wait
|
|
|
|
ar2.Destroy()
|
|
|
|
start := time.Now()
|
|
|
|
|
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
|
|
if upd.Count == 0 {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
last := upd.Allocs[upd.Count-1]
|
2016-01-22 00:05:35 +00:00
|
|
|
return last.ClientStatus != structs.AllocClientStatusPending, nil
|
2015-08-31 00:10:17 +00:00
|
|
|
}, func(err error) {
|
2015-11-14 06:07:13 +00:00
|
|
|
t.Fatalf("err: %v %#v %#v", err, upd.Allocs[0], ar.alloc.TaskStates)
|
2015-08-31 00:10:17 +00:00
|
|
|
})
|
|
|
|
|
2016-01-21 22:52:41 +00:00
|
|
|
if time.Since(start) > time.Duration(testutil.TestMultiplier()*15)*time.Second {
|
2015-08-31 00:10:17 +00:00
|
|
|
t.Fatalf("took too long to terminate")
|
|
|
|
}
|
2015-08-30 23:35:04 +00:00
|
|
|
}
|
2016-02-04 21:09:53 +00:00
|
|
|
|
|
|
|
func TestAllocRunner_SaveRestoreState_TerminalAlloc(t *testing.T) {
|
|
|
|
ctestutil.ExecCompatible(t)
|
|
|
|
upd, ar := testAllocRunner(false)
|
|
|
|
|
|
|
|
// Ensure task takes some time
|
|
|
|
task := ar.alloc.Job.TaskGroups[0].Tasks[0]
|
|
|
|
task.Config["command"] = "/bin/sleep"
|
|
|
|
task.Config["args"] = []string{"10"}
|
|
|
|
go ar.Run()
|
|
|
|
|
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
|
|
if upd.Count == 0 {
|
|
|
|
return false, fmt.Errorf("No updates")
|
|
|
|
}
|
|
|
|
last := upd.Allocs[upd.Count-1]
|
|
|
|
if last.ClientStatus == structs.AllocClientStatusRunning {
|
|
|
|
return false, fmt.Errorf("got status %v; want %v", last.ClientStatus, structs.AllocClientStatusRunning)
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}, func(err error) {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Update the alloc to be terminal which should cause the alloc runner to
|
|
|
|
// stop the tasks and wait for a destroy.
|
|
|
|
update := ar.alloc.Copy()
|
|
|
|
update.DesiredStatus = structs.AllocDesiredStatusStop
|
|
|
|
ar.Update(update)
|
|
|
|
|
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
|
|
return ar.alloc.DesiredStatus == structs.AllocDesiredStatusStop, nil
|
|
|
|
}, func(err error) {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
err := ar.SaveState()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-02-04 22:19:27 +00:00
|
|
|
// Ensure both alloc runners don't destroy
|
|
|
|
ar.destroy = true
|
|
|
|
|
2016-02-04 21:09:53 +00:00
|
|
|
// Create a new alloc runner
|
|
|
|
consulClient, err := NewConsulService(&consulServiceConfig{ar.logger, "127.0.0.1:8500", "", "", false, false, &structs.Node{}})
|
|
|
|
ar2 := NewAllocRunner(ar.logger, ar.config, upd.Update,
|
|
|
|
&structs.Allocation{ID: ar.alloc.ID}, consulClient)
|
|
|
|
err = ar2.RestoreState()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
go ar2.Run()
|
|
|
|
|
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
|
|
// Check the state still exists
|
|
|
|
if _, err := os.Stat(ar.stateFilePath()); err != nil {
|
|
|
|
return false, fmt.Errorf("state file destroyed: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the alloc directory still exists
|
|
|
|
if _, err := os.Stat(ar.ctx.AllocDir.AllocDir); err != nil {
|
|
|
|
return false, fmt.Errorf("alloc dir destroyed: %v", ar.ctx.AllocDir.AllocDir)
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}, func(err error) {
|
|
|
|
t.Fatalf("err: %v %#v %#v", err, upd.Allocs[0], ar.alloc.TaskStates)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Send the destroy signal and ensure the AllocRunner cleans up.
|
|
|
|
ar2.Destroy()
|
|
|
|
|
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
|
|
if upd.Count == 0 {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the status has changed.
|
|
|
|
last := upd.Allocs[upd.Count-1]
|
|
|
|
if last.ClientStatus != structs.AllocClientStatusDead {
|
|
|
|
return false, fmt.Errorf("got client status %v; want %v", last.ClientStatus, structs.AllocClientStatusDead)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the state was cleaned
|
|
|
|
if _, err := os.Stat(ar.stateFilePath()); err == nil {
|
|
|
|
return false, fmt.Errorf("state file still exists: %v", ar.stateFilePath())
|
|
|
|
} else if !os.IsNotExist(err) {
|
|
|
|
return false, fmt.Errorf("stat err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the alloc directory was cleaned
|
|
|
|
if _, err := os.Stat(ar.ctx.AllocDir.AllocDir); err == nil {
|
|
|
|
return false, fmt.Errorf("alloc dir still exists: %v", ar.ctx.AllocDir.AllocDir)
|
|
|
|
} else if !os.IsNotExist(err) {
|
|
|
|
return false, fmt.Errorf("stat err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}, func(err error) {
|
2016-02-04 22:19:27 +00:00
|
|
|
t.Fatalf("err: %v", err)
|
2016-02-04 21:09:53 +00:00
|
|
|
})
|
|
|
|
}
|