2018-02-05 16:05:59 -05:00
|
|
|
package container // import "github.com/docker/docker/container"
|
2014-06-06 07:28:12 -04:00
|
|
|
|
|
|
|
import (
|
2017-03-30 16:52:40 -04:00
|
|
|
"context"
|
2014-06-06 07:28:12 -04:00
|
|
|
"testing"
|
|
|
|
"time"
|
2016-07-15 14:21:19 -04:00
|
|
|
|
|
|
|
"github.com/docker/docker/api/types"
|
2022-05-10 15:59:00 -04:00
|
|
|
libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
|
2014-06-06 07:28:12 -04:00
|
|
|
)
|
|
|
|
|
2016-07-15 14:21:19 -04:00
|
|
|
func TestIsValidHealthString(t *testing.T) {
|
|
|
|
contexts := []struct {
|
|
|
|
Health string
|
|
|
|
Expected bool
|
|
|
|
}{
|
|
|
|
{types.Healthy, true},
|
|
|
|
{types.Unhealthy, true},
|
|
|
|
{types.Starting, true},
|
|
|
|
{types.NoHealthcheck, true},
|
|
|
|
{"fail", false},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, c := range contexts {
|
|
|
|
v := IsValidHealthString(c.Health)
|
|
|
|
if v != c.Expected {
|
|
|
|
t.Fatalf("Expected %t, but got %t", c.Expected, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-10 15:59:00 -04:00
|
|
|
type mockTask struct {
|
|
|
|
libcontainerdtypes.Task
|
|
|
|
pid uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *mockTask) Pid() uint32 { return t.pid }
|
|
|
|
|
2014-06-06 07:28:12 -04:00
|
|
|
func TestStateRunStop(t *testing.T) {
|
|
|
|
s := NewState()
|
2017-03-30 16:52:40 -04:00
|
|
|
|
2017-03-30 23:01:41 -04:00
|
|
|
// Begin another wait with WaitConditionRemoved. It should complete
|
2017-03-30 16:52:40 -04:00
|
|
|
// within 200 milliseconds.
|
2017-03-30 23:01:41 -04:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
2017-03-30 16:52:40 -04:00
|
|
|
defer cancel()
|
2017-03-30 23:01:41 -04:00
|
|
|
removalWait := s.Wait(ctx, WaitConditionRemoved)
|
2017-03-30 16:52:40 -04:00
|
|
|
|
|
|
|
// Full lifecycle two times.
|
|
|
|
for i := 1; i <= 2; i++ {
|
2017-03-30 23:01:41 -04:00
|
|
|
// A wait with WaitConditionNotRunning should return
|
|
|
|
// immediately since the state is now either "created" (on the
|
|
|
|
// first iteration) or "exited" (on the second iteration). It
|
|
|
|
// shouldn't take more than 50 milliseconds.
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
|
|
|
|
defer cancel()
|
|
|
|
// Expectx exit code to be i-1 since it should be the exit
|
|
|
|
// code from the previous loop or 0 for the created state.
|
|
|
|
if status := <-s.Wait(ctx, WaitConditionNotRunning); status.ExitCode() != i-1 {
|
|
|
|
t.Fatalf("ExitCode %v, expected %v, err %q", status.ExitCode(), i-1, status.Err())
|
|
|
|
}
|
|
|
|
|
|
|
|
// A wait with WaitConditionNextExit should block until the
|
|
|
|
// container has started and exited. It shouldn't take more
|
|
|
|
// than 100 milliseconds.
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
|
|
defer cancel()
|
|
|
|
initialWait := s.Wait(ctx, WaitConditionNextExit)
|
|
|
|
|
2017-03-30 16:52:40 -04:00
|
|
|
// Set the state to "Running".
|
2015-09-28 16:48:12 -04:00
|
|
|
s.Lock()
|
2022-05-10 15:59:00 -04:00
|
|
|
s.SetRunning(nil, &mockTask{pid: uint32(i)}, true)
|
2015-09-28 16:48:12 -04:00
|
|
|
s.Unlock()
|
2015-07-30 17:01:53 -04:00
|
|
|
|
2017-03-30 16:52:40 -04:00
|
|
|
// Assert desired state.
|
2014-06-06 07:28:12 -04:00
|
|
|
if !s.IsRunning() {
|
|
|
|
t.Fatal("State not running")
|
|
|
|
}
|
2017-03-30 16:52:40 -04:00
|
|
|
if s.Pid != i {
|
|
|
|
t.Fatalf("Pid %v, expected %v", s.Pid, i)
|
2014-06-06 07:28:12 -04:00
|
|
|
}
|
2016-06-14 14:11:43 -04:00
|
|
|
if s.ExitCode() != 0 {
|
|
|
|
t.Fatalf("ExitCode %v, expected 0", s.ExitCode())
|
2014-06-06 07:28:12 -04:00
|
|
|
}
|
|
|
|
|
2017-03-30 23:01:41 -04:00
|
|
|
// Now that it's running, a wait with WaitConditionNotRunning
|
|
|
|
// should block until we stop the container. It shouldn't take
|
|
|
|
// more than 100 milliseconds.
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
2017-03-30 16:52:40 -04:00
|
|
|
defer cancel()
|
2017-03-30 23:01:41 -04:00
|
|
|
exitWait := s.Wait(ctx, WaitConditionNotRunning)
|
2017-03-30 16:52:40 -04:00
|
|
|
|
|
|
|
// Set the state to "Exited".
|
2016-09-20 07:09:18 -04:00
|
|
|
s.Lock()
|
|
|
|
s.SetStopped(&ExitStatus{ExitCode: i})
|
|
|
|
s.Unlock()
|
2017-03-30 16:52:40 -04:00
|
|
|
|
|
|
|
// Assert desired state.
|
2014-06-06 07:28:12 -04:00
|
|
|
if s.IsRunning() {
|
|
|
|
t.Fatal("State is running")
|
|
|
|
}
|
2016-06-14 14:11:43 -04:00
|
|
|
if s.ExitCode() != i {
|
|
|
|
t.Fatalf("ExitCode %v, expected %v", s.ExitCode(), i)
|
2014-06-06 07:28:12 -04:00
|
|
|
}
|
|
|
|
if s.Pid != 0 {
|
|
|
|
t.Fatalf("Pid %v, expected 0", s.Pid)
|
|
|
|
}
|
2017-03-30 16:52:40 -04:00
|
|
|
|
2017-03-30 23:01:41 -04:00
|
|
|
// Receive the initialWait result.
|
|
|
|
if status := <-initialWait; status.ExitCode() != i {
|
2017-03-30 16:52:40 -04:00
|
|
|
t.Fatalf("ExitCode %v, expected %v, err %q", status.ExitCode(), i, status.Err())
|
2014-06-06 07:28:12 -04:00
|
|
|
}
|
2017-03-30 16:52:40 -04:00
|
|
|
|
2017-03-30 23:01:41 -04:00
|
|
|
// Receive the exitWait result.
|
|
|
|
if status := <-exitWait; status.ExitCode() != i {
|
2017-03-30 16:52:40 -04:00
|
|
|
t.Fatalf("ExitCode %v, expected %v, err %q", status.ExitCode(), i, status.Err())
|
2014-06-06 07:28:12 -04:00
|
|
|
}
|
|
|
|
}
|
2017-03-30 16:52:40 -04:00
|
|
|
|
|
|
|
// Set the state to dead and removed.
|
2019-08-25 08:34:30 -04:00
|
|
|
s.Lock()
|
|
|
|
s.Dead = true
|
|
|
|
s.Unlock()
|
2017-03-30 16:52:40 -04:00
|
|
|
s.SetRemoved()
|
|
|
|
|
|
|
|
// Wait for removed status or timeout.
|
2017-03-30 23:01:41 -04:00
|
|
|
if status := <-removalWait; status.ExitCode() != 2 {
|
2017-03-30 16:52:40 -04:00
|
|
|
// Should have the final exit code from the loop.
|
|
|
|
t.Fatalf("Removal wait exitCode %v, expected %v, err %q", status.ExitCode(), 2, status.Err())
|
|
|
|
}
|
2014-06-06 07:28:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestStateTimeoutWait(t *testing.T) {
|
|
|
|
s := NewState()
|
2017-03-30 16:52:40 -04:00
|
|
|
|
2017-03-30 23:01:41 -04:00
|
|
|
s.Lock()
|
2022-05-10 15:59:00 -04:00
|
|
|
s.SetRunning(nil, nil, true)
|
2017-03-30 23:01:41 -04:00
|
|
|
s.Unlock()
|
|
|
|
|
2017-03-30 16:52:40 -04:00
|
|
|
// Start a wait with a timeout.
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
|
|
defer cancel()
|
2017-03-30 23:01:41 -04:00
|
|
|
waitC := s.Wait(ctx, WaitConditionNotRunning)
|
2017-03-30 16:52:40 -04:00
|
|
|
|
|
|
|
// It should timeout *before* this 200ms timer does.
|
2014-06-06 07:28:12 -04:00
|
|
|
select {
|
|
|
|
case <-time.After(200 * time.Millisecond):
|
2016-08-10 04:02:08 -04:00
|
|
|
t.Fatal("Stop callback doesn't fire in 200 milliseconds")
|
2017-03-30 16:52:40 -04:00
|
|
|
case status := <-waitC:
|
2016-04-25 23:50:12 -04:00
|
|
|
t.Log("Stop callback fired")
|
2017-03-30 16:52:40 -04:00
|
|
|
// Should be a timeout error.
|
|
|
|
if status.Err() == nil {
|
|
|
|
t.Fatal("expected timeout error, got nil")
|
|
|
|
}
|
|
|
|
if status.ExitCode() != -1 {
|
|
|
|
t.Fatalf("expected exit code %v, got %v", -1, status.ExitCode())
|
|
|
|
}
|
2014-06-06 07:28:12 -04:00
|
|
|
}
|
2015-07-30 17:01:53 -04:00
|
|
|
|
2016-09-20 07:09:18 -04:00
|
|
|
s.Lock()
|
2017-03-30 16:52:40 -04:00
|
|
|
s.SetStopped(&ExitStatus{ExitCode: 0})
|
2016-09-20 07:09:18 -04:00
|
|
|
s.Unlock()
|
2015-07-30 17:01:53 -04:00
|
|
|
|
2017-03-30 16:52:40 -04:00
|
|
|
// Start another wait with a timeout. This one should return
|
|
|
|
// immediately.
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
|
|
defer cancel()
|
2017-03-30 23:01:41 -04:00
|
|
|
waitC = s.Wait(ctx, WaitConditionNotRunning)
|
2017-03-30 16:52:40 -04:00
|
|
|
|
2014-06-06 07:28:12 -04:00
|
|
|
select {
|
|
|
|
case <-time.After(200 * time.Millisecond):
|
2016-11-24 07:01:00 -05:00
|
|
|
t.Fatal("Stop callback doesn't fire in 200 milliseconds")
|
2017-03-30 16:52:40 -04:00
|
|
|
case status := <-waitC:
|
2016-04-25 23:50:12 -04:00
|
|
|
t.Log("Stop callback fired")
|
2017-03-30 16:52:40 -04:00
|
|
|
if status.ExitCode() != 0 {
|
|
|
|
t.Fatalf("expected exit code %v, got %v, err %q", 0, status.ExitCode(), status.Err())
|
|
|
|
}
|
2014-06-06 07:28:12 -04:00
|
|
|
}
|
|
|
|
}
|
2017-10-23 21:49:58 -04:00
|
|
|
|
2022-07-19 06:59:46 -04:00
|
|
|
// Related issue: #39352
|
|
|
|
func TestCorrectStateWaitResultAfterRestart(t *testing.T) {
|
|
|
|
s := NewState()
|
|
|
|
|
|
|
|
s.Lock()
|
2022-05-10 15:59:00 -04:00
|
|
|
s.SetRunning(nil, nil, true)
|
2022-07-19 06:59:46 -04:00
|
|
|
s.Unlock()
|
|
|
|
|
|
|
|
waitC := s.Wait(context.Background(), WaitConditionNotRunning)
|
|
|
|
want := ExitStatus{ExitCode: 10, ExitedAt: time.Now()}
|
|
|
|
|
|
|
|
s.Lock()
|
|
|
|
s.SetRestarting(&want)
|
|
|
|
s.Unlock()
|
|
|
|
|
|
|
|
s.Lock()
|
2022-05-10 15:59:00 -04:00
|
|
|
s.SetRunning(nil, nil, true)
|
2022-07-19 06:59:46 -04:00
|
|
|
s.Unlock()
|
|
|
|
|
|
|
|
got := <-waitC
|
|
|
|
if got.exitCode != want.ExitCode {
|
|
|
|
t.Fatalf("expected exit code %v, got %v", want.ExitCode, got.exitCode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-23 21:49:58 -04:00
|
|
|
func TestIsValidStateString(t *testing.T) {
|
|
|
|
states := []struct {
|
|
|
|
state string
|
|
|
|
expected bool
|
|
|
|
}{
|
|
|
|
{"paused", true},
|
|
|
|
{"restarting", true},
|
|
|
|
{"running", true},
|
|
|
|
{"dead", true},
|
|
|
|
{"start", false},
|
|
|
|
{"created", true},
|
|
|
|
{"exited", true},
|
|
|
|
{"removing", true},
|
|
|
|
{"stop", false},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, s := range states {
|
|
|
|
v := IsValidStateString(s.state)
|
|
|
|
if v != s.expected {
|
|
|
|
t.Fatalf("Expected %t, but got %t", s.expected, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|