github.com/rawahars/moby@v24.0.4+incompatible/integration/container/stop_linux_test.go (about)

     1  package container // import "github.com/docker/docker/integration/container"
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"strconv"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/docker/docker/api/types"
    14  	containertypes "github.com/docker/docker/api/types/container"
    15  	"github.com/docker/docker/client"
    16  	"github.com/docker/docker/errdefs"
    17  	"github.com/docker/docker/integration/internal/container"
    18  	"github.com/docker/docker/pkg/stdcopy"
    19  	"gotest.tools/v3/assert"
    20  	is "gotest.tools/v3/assert/cmp"
    21  	"gotest.tools/v3/icmd"
    22  	"gotest.tools/v3/poll"
    23  	"gotest.tools/v3/skip"
    24  )
    25  
    26  // TestStopContainerWithTimeout checks that ContainerStop with
    27  // a timeout works as documented, i.e. in case of negative timeout
    28  // waiting is not limited (issue #35311).
    29  func TestStopContainerWithTimeout(t *testing.T) {
    30  	defer setupTest(t)()
    31  	client := testEnv.APIClient()
    32  	ctx := context.Background()
    33  
    34  	testCmd := container.WithCmd("sh", "-c", "sleep 2 && exit 42")
    35  	testData := []struct {
    36  		doc              string
    37  		timeout          int
    38  		expectedExitCode int
    39  	}{
    40  		// In case container is forcefully killed, 137 is returned,
    41  		// otherwise the exit code from the above script
    42  		{
    43  			"zero timeout: expect forceful container kill",
    44  			0, 137,
    45  		},
    46  		{
    47  			"too small timeout: expect forceful container kill",
    48  			1, 137,
    49  		},
    50  		{
    51  			"big enough timeout: expect graceful container stop",
    52  			3, 42,
    53  		},
    54  		{
    55  			"unlimited timeout: expect graceful container stop",
    56  			-1, 42,
    57  		},
    58  	}
    59  
    60  	for _, d := range testData {
    61  		d := d
    62  		t.Run(strconv.Itoa(d.timeout), func(t *testing.T) {
    63  			t.Parallel()
    64  			id := container.Run(ctx, t, client, testCmd)
    65  
    66  			err := client.ContainerStop(ctx, id, containertypes.StopOptions{Timeout: &d.timeout})
    67  			assert.NilError(t, err)
    68  
    69  			poll.WaitOn(t, container.IsStopped(ctx, client, id),
    70  				poll.WithDelay(100*time.Millisecond))
    71  
    72  			inspect, err := client.ContainerInspect(ctx, id)
    73  			assert.NilError(t, err)
    74  			assert.Equal(t, inspect.State.ExitCode, d.expectedExitCode)
    75  		})
    76  	}
    77  }
    78  
    79  func TestDeleteDevicemapper(t *testing.T) {
    80  	skip.If(t, testEnv.DaemonInfo.Driver != "devicemapper")
    81  	skip.If(t, testEnv.IsRemoteDaemon)
    82  
    83  	defer setupTest(t)()
    84  	client := testEnv.APIClient()
    85  	ctx := context.Background()
    86  
    87  	id := container.Run(ctx, t, client, container.WithName("foo-"+t.Name()), container.WithCmd("echo"))
    88  
    89  	poll.WaitOn(t, container.IsStopped(ctx, client, id), poll.WithDelay(100*time.Millisecond))
    90  
    91  	inspect, err := client.ContainerInspect(ctx, id)
    92  	assert.NilError(t, err)
    93  
    94  	deviceID := inspect.GraphDriver.Data["DeviceId"]
    95  
    96  	// Find pool name from device name
    97  	deviceName := inspect.GraphDriver.Data["DeviceName"]
    98  	devicePrefix := deviceName[:strings.LastIndex(deviceName, "-")]
    99  	devicePool := fmt.Sprintf("/dev/mapper/%s-pool", devicePrefix)
   100  
   101  	result := icmd.RunCommand("dmsetup", "message", devicePool, "0", fmt.Sprintf("delete %s", deviceID))
   102  	result.Assert(t, icmd.Success)
   103  
   104  	err = client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{})
   105  	assert.NilError(t, err)
   106  }
   107  
   108  // TestStopContainerWithTimeoutCancel checks that ContainerStop is not cancelled
   109  // if the request is cancelled.
   110  // See issue https://github.com/moby/moby/issues/45731
   111  func TestStopContainerWithTimeoutCancel(t *testing.T) {
   112  	t.Parallel()
   113  	defer setupTest(t)()
   114  	apiClient := testEnv.APIClient()
   115  	t.Cleanup(func() { _ = apiClient.Close() })
   116  
   117  	ctx := context.Background()
   118  	id := container.Run(ctx, t, apiClient,
   119  		container.WithCmd("sh", "-c", "trap 'echo received TERM' TERM; while true; do usleep 10; done"),
   120  	)
   121  	poll.WaitOn(t, container.IsInState(ctx, apiClient, id, "running"))
   122  
   123  	ctxCancel, cancel := context.WithCancel(context.Background())
   124  	t.Cleanup(cancel)
   125  	const stopTimeout = 3
   126  
   127  	stoppedCh := make(chan error)
   128  	go func() {
   129  		sto := stopTimeout
   130  		stoppedCh <- apiClient.ContainerStop(ctxCancel, id, containertypes.StopOptions{Timeout: &sto})
   131  	}()
   132  
   133  	poll.WaitOn(t, logsContains(ctx, apiClient, id, "received TERM"))
   134  
   135  	// Cancel the context once we verified the container was signaled, and check
   136  	// that the container is not killed immediately
   137  	cancel()
   138  
   139  	select {
   140  	case stoppedErr := <-stoppedCh:
   141  		assert.Check(t, is.ErrorType(stoppedErr, errdefs.IsCancelled))
   142  	case <-time.After(5 * time.Second):
   143  		t.Fatal("timeout waiting for stop request to be cancelled")
   144  	}
   145  	inspect, err := apiClient.ContainerInspect(ctx, id)
   146  	assert.Check(t, err)
   147  	assert.Check(t, inspect.State.Running)
   148  
   149  	// container should be stopped after stopTimeout is reached. The daemon.containerStop
   150  	// code is rather convoluted, and waits another 2 seconds for the container to
   151  	// terminate after signaling it;
   152  	// https://github.com/moby/moby/blob/97455cc31ffa08078db6591f018256ed59c35bbc/daemon/stop.go#L101-L112
   153  	//
   154  	// Adding 3 seconds to the specified stopTimeout to take this into account,
   155  	// and add another second margin to try to avoid flakiness.
   156  	poll.WaitOn(t, container.IsStopped(ctx, apiClient, id), poll.WithTimeout((3+stopTimeout)*time.Second))
   157  }
   158  
   159  // logsContains verifies the container contains the given text in the log's stdout.
   160  func logsContains(ctx context.Context, client client.APIClient, containerID string, logString string) func(log poll.LogT) poll.Result {
   161  	return func(log poll.LogT) poll.Result {
   162  		logs, err := client.ContainerLogs(ctx, containerID, types.ContainerLogsOptions{
   163  			ShowStdout: true,
   164  		})
   165  		if err != nil {
   166  			return poll.Error(err)
   167  		}
   168  		defer logs.Close()
   169  
   170  		var stdout bytes.Buffer
   171  		_, err = stdcopy.StdCopy(&stdout, io.Discard, logs)
   172  		if err != nil {
   173  			return poll.Error(err)
   174  		}
   175  		if strings.Contains(stdout.String(), logString) {
   176  			return poll.Success()
   177  		}
   178  		return poll.Continue("waiting for logstring '%s' in container", logString)
   179  	}
   180  }