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 }