github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration/container/health_test.go (about)

     1  package container // import "github.com/Prakhar-Agarwal-byte/moby/integration/container"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/Prakhar-Agarwal-byte/moby/api/types"
    10  	containertypes "github.com/Prakhar-Agarwal-byte/moby/api/types/container"
    11  	"github.com/Prakhar-Agarwal-byte/moby/client"
    12  	"github.com/Prakhar-Agarwal-byte/moby/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.DaemonInfo.OSType == "windows", "FIXME")
    22  	ctx := setupTest(t)
    23  	apiClient := testEnv.APIClient()
    24  
    25  	cID := container.Run(ctx, t, apiClient, container.WithTty(true), container.WithWorkingDir("/foo"), func(c *container.TestContainerConfig) {
    26  		c.Config.Healthcheck = &containertypes.HealthConfig{
    27  			Test:     []string{"CMD-SHELL", "if [ \"$PWD\" = \"/foo\" ]; then exit 0; else exit 1; fi;"},
    28  			Interval: 50 * time.Millisecond,
    29  			Retries:  3,
    30  		}
    31  	})
    32  
    33  	poll.WaitOn(t, pollForHealthStatus(ctx, apiClient, cID, types.Healthy), poll.WithDelay(100*time.Millisecond))
    34  }
    35  
    36  // GitHub #37263
    37  // Do not stop healthchecks just because we sent a signal to the container
    38  func TestHealthKillContainer(t *testing.T) {
    39  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "Windows only supports SIGKILL and SIGTERM? See https://github.com/moby/moby/issues/39574")
    40  	ctx := setupTest(t)
    41  
    42  	apiClient := testEnv.APIClient()
    43  
    44  	id := container.Run(ctx, t, apiClient, func(c *container.TestContainerConfig) {
    45  		cmd := `
    46  # Set the initial HEALTH value so the healthcheck passes
    47  HEALTH="1"
    48  echo $HEALTH > /health
    49  
    50  # Any time doHealth is run we flip the value
    51  # This lets us use kill signals to determine when healtchecks have run.
    52  doHealth() {
    53  	case "$HEALTH" in
    54  		"0")
    55  			HEALTH="1"
    56  			;;
    57  		"1")
    58  			HEALTH="0"
    59  			;;
    60  	esac
    61  	echo $HEALTH > /health
    62  }
    63  
    64  trap 'doHealth' USR1
    65  
    66  while true; do sleep 1; done
    67  `
    68  		c.Config.Cmd = []string{"/bin/sh", "-c", cmd}
    69  		c.Config.Healthcheck = &containertypes.HealthConfig{
    70  			Test:     []string{"CMD-SHELL", `[ "$(cat /health)" = "1" ]`},
    71  			Interval: time.Second,
    72  			Retries:  5,
    73  		}
    74  	})
    75  
    76  	ctxPoll, cancel := context.WithTimeout(ctx, 30*time.Second)
    77  	defer cancel()
    78  	poll.WaitOn(t, pollForHealthStatus(ctxPoll, apiClient, id, "healthy"), poll.WithDelay(100*time.Millisecond))
    79  
    80  	err := apiClient.ContainerKill(ctx, id, "SIGUSR1")
    81  	assert.NilError(t, err)
    82  
    83  	ctxPoll, cancel = context.WithTimeout(ctx, 30*time.Second)
    84  	defer cancel()
    85  	poll.WaitOn(t, pollForHealthStatus(ctxPoll, apiClient, id, "unhealthy"), poll.WithDelay(100*time.Millisecond))
    86  
    87  	err = apiClient.ContainerKill(ctx, id, "SIGUSR1")
    88  	assert.NilError(t, err)
    89  
    90  	ctxPoll, cancel = context.WithTimeout(ctx, 30*time.Second)
    91  	defer cancel()
    92  	poll.WaitOn(t, pollForHealthStatus(ctxPoll, apiClient, id, "healthy"), poll.WithDelay(100*time.Millisecond))
    93  }
    94  
    95  // TestHealthCheckProcessKilled verifies that health-checks exec get killed on time-out.
    96  func TestHealthCheckProcessKilled(t *testing.T) {
    97  	ctx := setupTest(t)
    98  	apiClient := testEnv.APIClient()
    99  
   100  	cID := container.Run(ctx, t, apiClient, func(c *container.TestContainerConfig) {
   101  		c.Config.Healthcheck = &containertypes.HealthConfig{
   102  			Test:     []string{"CMD", "sh", "-c", `echo "logs logs logs"; sleep 60`},
   103  			Interval: 100 * time.Millisecond,
   104  			Timeout:  50 * time.Millisecond,
   105  			Retries:  1,
   106  		}
   107  	})
   108  	poll.WaitOn(t, pollForHealthCheckLog(ctx, apiClient, cID, "Health check exceeded timeout (50ms): logs logs logs\n"))
   109  }
   110  
   111  func TestHealthStartInterval(t *testing.T) {
   112  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "The shell commands used in the test healthcheck do not work on Windows")
   113  	ctx := setupTest(t)
   114  	apiClient := testEnv.APIClient()
   115  
   116  	// Note: Windows is much slower than linux so this use longer intervals/timeouts
   117  	id := container.Run(ctx, t, apiClient, func(c *container.TestContainerConfig) {
   118  		c.Config.Healthcheck = &containertypes.HealthConfig{
   119  			Test:          []string{"CMD-SHELL", `count="$(cat /tmp/health)"; if [ -z "${count}" ]; then let count=0; fi; let count=${count}+1; echo -n ${count} | tee /tmp/health; if [ ${count} -lt 3 ]; then exit 1; fi`},
   120  			Interval:      30 * time.Second,
   121  			StartInterval: time.Second,
   122  			StartPeriod:   30 * time.Second,
   123  		}
   124  	})
   125  
   126  	ctxPoll, cancel := context.WithTimeout(ctx, 30*time.Second)
   127  	defer cancel()
   128  
   129  	dl, _ := ctxPoll.Deadline()
   130  
   131  	poll.WaitOn(t, func(log poll.LogT) poll.Result {
   132  		if ctxPoll.Err() != nil {
   133  			return poll.Error(ctxPoll.Err())
   134  		}
   135  		inspect, err := apiClient.ContainerInspect(ctxPoll, id)
   136  		if err != nil {
   137  			return poll.Error(err)
   138  		}
   139  		if inspect.State.Health.Status != "healthy" {
   140  			if len(inspect.State.Health.Log) > 0 {
   141  				t.Log(inspect.State.Health.Log[len(inspect.State.Health.Log)-1])
   142  			}
   143  			return poll.Continue("waiting on container to be ready")
   144  		}
   145  		return poll.Success()
   146  	}, poll.WithDelay(100*time.Millisecond), poll.WithTimeout(time.Until(dl)))
   147  	cancel()
   148  
   149  	ctxPoll, cancel = context.WithTimeout(ctx, 2*time.Minute)
   150  	defer cancel()
   151  	dl, _ = ctxPoll.Deadline()
   152  
   153  	poll.WaitOn(t, func(log poll.LogT) poll.Result {
   154  		inspect, err := apiClient.ContainerInspect(ctxPoll, id)
   155  		if err != nil {
   156  			return poll.Error(err)
   157  		}
   158  
   159  		hLen := len(inspect.State.Health.Log)
   160  		if hLen < 2 {
   161  			return poll.Continue("waiting for more healthcheck results")
   162  		}
   163  
   164  		h1 := inspect.State.Health.Log[hLen-1]
   165  		h2 := inspect.State.Health.Log[hLen-2]
   166  		if h1.Start.Sub(h2.Start) >= inspect.Config.Healthcheck.Interval {
   167  			return poll.Success()
   168  		}
   169  		t.Log(h1.Start.Sub(h2.Start))
   170  		return poll.Continue("waiting for health check interval to switch from the start interval")
   171  	}, poll.WithDelay(time.Second), poll.WithTimeout(time.Until(dl)))
   172  }
   173  
   174  func pollForHealthCheckLog(ctx context.Context, client client.APIClient, containerID string, expected string) func(log poll.LogT) poll.Result {
   175  	return func(log poll.LogT) poll.Result {
   176  		inspect, err := client.ContainerInspect(ctx, containerID)
   177  		if err != nil {
   178  			return poll.Error(err)
   179  		}
   180  		healthChecksTotal := len(inspect.State.Health.Log)
   181  		if healthChecksTotal > 0 {
   182  			output := inspect.State.Health.Log[healthChecksTotal-1].Output
   183  			if output == expected {
   184  				return poll.Success()
   185  			}
   186  			return poll.Error(fmt.Errorf("expected %q, got %q", expected, output))
   187  		}
   188  		return poll.Continue("waiting for container healthcheck logs")
   189  	}
   190  }
   191  
   192  func pollForHealthStatus(ctx context.Context, client client.APIClient, containerID string, healthStatus string) func(log poll.LogT) poll.Result {
   193  	return func(log poll.LogT) poll.Result {
   194  		inspect, err := client.ContainerInspect(ctx, containerID)
   195  
   196  		switch {
   197  		case err != nil:
   198  			return poll.Error(err)
   199  		case inspect.State.Health.Status == healthStatus:
   200  			return poll.Success()
   201  		default:
   202  			return poll.Continue("waiting for container to become %s", healthStatus)
   203  		}
   204  	}
   205  }