gopkg.in/docker/docker.v20@v20.10.27/integration/container/health_test.go (about)

     1  package container // import "github.com/docker/docker/integration/container"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/docker/docker/api/types"
    10  	containertypes "github.com/docker/docker/api/types/container"
    11  	"github.com/docker/docker/client"
    12  	"github.com/docker/docker/integration/internal/container"
    13  	"gotest.tools/v3/assert"
    14  	"gotest.tools/v3/poll"
    15  	"gotest.tools/v3/skip"
    16  )
    17  
    18  // TestHealthCheckWorkdir verifies that health-checks inherit the containers'
    19  // working-dir.
    20  func TestHealthCheckWorkdir(t *testing.T) {
    21  	skip.If(t, testEnv.OSType == "windows", "FIXME")
    22  	defer setupTest(t)()
    23  	ctx := context.Background()
    24  	client := testEnv.APIClient()
    25  
    26  	cID := container.Run(ctx, t, client, container.WithTty(true), container.WithWorkingDir("/foo"), func(c *container.TestContainerConfig) {
    27  		c.Config.Healthcheck = &containertypes.HealthConfig{
    28  			Test:     []string{"CMD-SHELL", "if [ \"$PWD\" = \"/foo\" ]; then exit 0; else exit 1; fi;"},
    29  			Interval: 50 * time.Millisecond,
    30  			Retries:  3,
    31  		}
    32  	})
    33  
    34  	poll.WaitOn(t, pollForHealthStatus(ctx, client, cID, types.Healthy), poll.WithDelay(100*time.Millisecond))
    35  }
    36  
    37  // GitHub #37263
    38  // Do not stop healthchecks just because we sent a signal to the container
    39  func TestHealthKillContainer(t *testing.T) {
    40  	skip.If(t, testEnv.OSType == "windows", "Windows only supports SIGKILL and SIGTERM? See https://github.com/moby/moby/issues/39574")
    41  	defer setupTest(t)()
    42  
    43  	ctx := context.Background()
    44  	client := testEnv.APIClient()
    45  
    46  	id := container.Run(ctx, t, client, func(c *container.TestContainerConfig) {
    47  		cmd := `
    48  # Set the initial HEALTH value so the healthcheck passes
    49  HEALTH="1"
    50  echo $HEALTH > /health
    51  
    52  # Any time doHealth is run we flip the value
    53  # This lets us use kill signals to determine when healtchecks have run.
    54  doHealth() {
    55  	case "$HEALTH" in
    56  		"0")
    57  			HEALTH="1"
    58  			;;
    59  		"1")
    60  			HEALTH="0"
    61  			;;
    62  	esac
    63  	echo $HEALTH > /health
    64  }
    65  
    66  trap 'doHealth' USR1
    67  
    68  while true; do sleep 1; done
    69  `
    70  		c.Config.Cmd = []string{"/bin/sh", "-c", cmd}
    71  		c.Config.Healthcheck = &containertypes.HealthConfig{
    72  			Test:     []string{"CMD-SHELL", `[ "$(cat /health)" = "1" ]`},
    73  			Interval: time.Second,
    74  			Retries:  5,
    75  		}
    76  	})
    77  
    78  	ctxPoll, cancel := context.WithTimeout(ctx, 30*time.Second)
    79  	defer cancel()
    80  	poll.WaitOn(t, pollForHealthStatus(ctxPoll, client, id, "healthy"), poll.WithDelay(100*time.Millisecond))
    81  
    82  	err := client.ContainerKill(ctx, id, "SIGUSR1")
    83  	assert.NilError(t, err)
    84  
    85  	ctxPoll, cancel = context.WithTimeout(ctx, 30*time.Second)
    86  	defer cancel()
    87  	poll.WaitOn(t, pollForHealthStatus(ctxPoll, client, id, "unhealthy"), poll.WithDelay(100*time.Millisecond))
    88  
    89  	err = client.ContainerKill(ctx, id, "SIGUSR1")
    90  	assert.NilError(t, err)
    91  
    92  	ctxPoll, cancel = context.WithTimeout(ctx, 30*time.Second)
    93  	defer cancel()
    94  	poll.WaitOn(t, pollForHealthStatus(ctxPoll, client, id, "healthy"), poll.WithDelay(100*time.Millisecond))
    95  }
    96  
    97  // TestHealthCheckProcessKilled verifies that health-checks exec get killed on time-out.
    98  func TestHealthCheckProcessKilled(t *testing.T) {
    99  	// FIXME: Broken on Windows + containerd combination
   100  	defer setupTest(t)()
   101  	ctx := context.Background()
   102  	apiClient := testEnv.APIClient()
   103  
   104  	cID := container.Run(ctx, t, apiClient, func(c *container.TestContainerConfig) {
   105  		c.Config.Healthcheck = &containertypes.HealthConfig{
   106  			Test:     []string{"CMD", "sh", "-c", "sleep 60"},
   107  			Interval: 100 * time.Millisecond,
   108  			Timeout:  50 * time.Millisecond,
   109  			Retries:  1,
   110  		}
   111  	})
   112  	poll.WaitOn(t, pollForHealthCheckLog(ctx, apiClient, cID, "Health check exceeded timeout (50ms)"))
   113  }
   114  
   115  func pollForHealthCheckLog(ctx context.Context, client client.APIClient, containerID string, expected string) func(log poll.LogT) poll.Result {
   116  	return func(log poll.LogT) poll.Result {
   117  		inspect, err := client.ContainerInspect(ctx, containerID)
   118  		if err != nil {
   119  			return poll.Error(err)
   120  		}
   121  		healthChecksTotal := len(inspect.State.Health.Log)
   122  		if healthChecksTotal > 0 {
   123  			output := inspect.State.Health.Log[healthChecksTotal-1].Output
   124  			if output == expected {
   125  				return poll.Success()
   126  			}
   127  			return poll.Error(fmt.Errorf("expected %q, got %q", expected, output))
   128  		}
   129  		return poll.Continue("waiting for container healthcheck logs")
   130  	}
   131  }
   132  
   133  func pollForHealthStatus(ctx context.Context, client client.APIClient, containerID string, healthStatus string) func(log poll.LogT) poll.Result {
   134  	return func(log poll.LogT) poll.Result {
   135  		inspect, err := client.ContainerInspect(ctx, containerID)
   136  
   137  		switch {
   138  		case err != nil:
   139  			return poll.Error(err)
   140  		case inspect.State.Health.Status == healthStatus:
   141  			return poll.Success()
   142  		default:
   143  			return poll.Continue("waiting for container to become %s", healthStatus)
   144  		}
   145  	}
   146  }