github.com/containers/podman/v5@v5.1.0-rc1/test/e2e/healthcheck_run_test.go (about) 1 package integration 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strconv" 8 "time" 9 10 "github.com/containers/podman/v5/libpod/define" 11 . "github.com/containers/podman/v5/test/utils" 12 . "github.com/onsi/ginkgo/v2" 13 . "github.com/onsi/gomega" 14 . "github.com/onsi/gomega/gexec" 15 ) 16 17 var _ = Describe("Podman healthcheck run", func() { 18 19 It("podman healthcheck run bogus container", func() { 20 session := podmanTest.Podman([]string{"healthcheck", "run", "foobar"}) 21 session.WaitWithDefaultTimeout() 22 Expect(session).To(ExitWithError(125, `unable to look up foobar to perform a health check: no container with name or ID "foobar" found: no such container`)) 23 }) 24 25 It("podman disable healthcheck with --no-healthcheck on valid container", func() { 26 session := podmanTest.Podman([]string{"run", "-dt", "--no-healthcheck", "--name", "hc", HEALTHCHECK_IMAGE}) 27 session.WaitWithDefaultTimeout() 28 Expect(session).Should(ExitCleanly()) 29 hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 30 hc.WaitWithDefaultTimeout() 31 Expect(hc).Should(ExitWithError(125, "has no defined healthcheck")) 32 }) 33 34 It("podman disable healthcheck with --no-healthcheck must not show starting on status", func() { 35 session := podmanTest.Podman([]string{"run", "-dt", "--no-healthcheck", "--name", "hc", HEALTHCHECK_IMAGE}) 36 session.WaitWithDefaultTimeout() 37 Expect(session).Should(ExitCleanly()) 38 hc := podmanTest.Podman([]string{"container", "inspect", "--format", "{{.State.Health}}", "hc"}) 39 hc.WaitWithDefaultTimeout() 40 Expect(hc).Should(ExitCleanly()) 41 Expect(hc.OutputToString()).To(Equal("<nil>")) 42 }) 43 44 It("podman run healthcheck and logs should contain healthcheck output", func() { 45 session := podmanTest.Podman([]string{"run", "--name", "test-logs", "-dt", "--health-interval", "1s", "--health-cmd", "echo working", "busybox", "sleep", "3600"}) 46 session.WaitWithDefaultTimeout() 47 Expect(session).Should(ExitCleanly()) 48 49 // Buy a little time to get container running 50 for i := 0; i < 5; i++ { 51 hc := podmanTest.Podman([]string{"healthcheck", "run", "test-logs"}) 52 hc.WaitWithDefaultTimeout() 53 exitCode := hc.ExitCode() 54 if exitCode == 0 || i == 4 { 55 break 56 } 57 time.Sleep(1 * time.Second) 58 } 59 60 hc := podmanTest.Podman([]string{"container", "inspect", "--format", "{{.State.Healthcheck.Log}}", "test-logs"}) 61 hc.WaitWithDefaultTimeout() 62 Expect(hc).Should(ExitCleanly()) 63 Expect(hc.OutputToString()).To(ContainSubstring("working")) 64 }) 65 66 It("podman healthcheck from image's config (not container config)", func() { 67 // Regression test for #12226: a health check may be defined in 68 // the container or the container-config of an image. 69 session := podmanTest.Podman([]string{"create", "-q", "--name", "hc", "quay.io/libpod/healthcheck:config-only", "ls"}) 70 session.WaitWithDefaultTimeout() 71 Expect(session).Should(ExitCleanly()) 72 hc := podmanTest.Podman([]string{"container", "inspect", "--format", "{{.Config.Healthcheck}}", "hc"}) 73 hc.WaitWithDefaultTimeout() 74 Expect(hc).Should(ExitCleanly()) 75 Expect(hc.OutputToString()).To(Equal("{[CMD-SHELL curl -f http://localhost/ || exit 1] 0s 0s 5m0s 3s 0}")) 76 }) 77 78 It("podman disable healthcheck with --health-cmd=none on valid container", func() { 79 session := podmanTest.Podman([]string{"run", "-dt", "--health-cmd", "none", "--name", "hc", HEALTHCHECK_IMAGE}) 80 session.WaitWithDefaultTimeout() 81 Expect(session).Should(ExitCleanly()) 82 hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 83 hc.WaitWithDefaultTimeout() 84 Expect(hc).Should(ExitWithError(125, "has no defined healthcheck")) 85 }) 86 87 It("podman healthcheck on valid container", func() { 88 Skip("Extremely consistent flake - re-enable on debugging") 89 session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", HEALTHCHECK_IMAGE}) 90 session.WaitWithDefaultTimeout() 91 Expect(session).Should(ExitCleanly()) 92 93 exitCode := 999 94 95 // Buy a little time to get container running 96 for i := 0; i < 5; i++ { 97 hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 98 hc.WaitWithDefaultTimeout() 99 exitCode = hc.ExitCode() 100 if exitCode == 0 || i == 4 { 101 break 102 } 103 time.Sleep(1 * time.Second) 104 } 105 Expect(exitCode).To(Equal(0)) 106 107 ps := podmanTest.Podman([]string{"ps"}) 108 ps.WaitWithDefaultTimeout() 109 Expect(ps).Should(ExitCleanly()) 110 Expect(ps.OutputToString()).To(ContainSubstring("(healthy)")) 111 }) 112 113 It("podman healthcheck that should fail", func() { 114 session := podmanTest.Podman([]string{"run", "-q", "-dt", "--name", "hc", "quay.io/libpod/badhealthcheck:latest"}) 115 session.WaitWithDefaultTimeout() 116 Expect(session).Should(ExitCleanly()) 117 118 hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 119 hc.WaitWithDefaultTimeout() 120 Expect(hc).Should(Exit(1)) 121 }) 122 123 It("podman healthcheck on stopped container", func() { 124 session := podmanTest.Podman([]string{"run", "--name", "hc", HEALTHCHECK_IMAGE, "ls"}) 125 session.WaitWithDefaultTimeout() 126 Expect(session).Should(ExitCleanly()) 127 128 hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 129 hc.WaitWithDefaultTimeout() 130 Expect(hc).Should(ExitWithError(125, "is not running")) 131 }) 132 133 It("podman healthcheck on container without healthcheck", func() { 134 session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", ALPINE, "top"}) 135 session.WaitWithDefaultTimeout() 136 Expect(session).Should(ExitCleanly()) 137 138 hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 139 hc.WaitWithDefaultTimeout() 140 Expect(hc).Should(ExitWithError(125, "has no defined healthcheck")) 141 }) 142 143 It("podman healthcheck should be starting", func() { 144 session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-retries", "2", "--health-cmd", "ls /foo || exit 1", ALPINE, "top"}) 145 session.WaitWithDefaultTimeout() 146 Expect(session).Should(ExitCleanly()) 147 inspect := podmanTest.InspectContainer("hc") 148 Expect(inspect[0].State.Health).To(HaveField("Status", "starting")) 149 }) 150 151 It("podman healthcheck failed checks in start-period should not change status", func() { 152 session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-start-period", "2m", "--health-retries", "2", "--health-cmd", "ls /foo || exit 1", ALPINE, "top"}) 153 session.WaitWithDefaultTimeout() 154 Expect(session).Should(ExitCleanly()) 155 156 hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 157 hc.WaitWithDefaultTimeout() 158 Expect(hc).Should(Exit(1)) 159 160 hc = podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 161 hc.WaitWithDefaultTimeout() 162 Expect(hc).Should(Exit(1)) 163 164 hc = podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 165 hc.WaitWithDefaultTimeout() 166 Expect(hc).Should(Exit(1)) 167 168 inspect := podmanTest.InspectContainer("hc") 169 Expect(inspect[0].State.Health).To(HaveField("Status", "starting")) 170 // test old podman compat (see #11645) 171 Expect(inspect[0].State.Healthcheck()).To(HaveField("Status", "starting")) 172 }) 173 174 It("podman healthcheck failed checks must reach retries before unhealthy ", func() { 175 session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-retries", "2", "--health-cmd", "ls /foo || exit 1", ALPINE, "top"}) 176 session.WaitWithDefaultTimeout() 177 Expect(session).Should(ExitCleanly()) 178 179 hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 180 hc.WaitWithDefaultTimeout() 181 Expect(hc).Should(Exit(1)) 182 183 inspect := podmanTest.InspectContainer("hc") 184 Expect(inspect[0].State.Health).To(HaveField("Status", "starting")) 185 186 hc = podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 187 hc.WaitWithDefaultTimeout() 188 Expect(hc).Should(Exit(1)) 189 190 inspect = podmanTest.InspectContainer("hc") 191 Expect(inspect[0].State.Health).To(HaveField("Status", define.HealthCheckUnhealthy)) 192 // test old podman compat (see #11645) 193 Expect(inspect[0].State.Healthcheck()).To(HaveField("Status", define.HealthCheckUnhealthy)) 194 }) 195 196 It("podman healthcheck good check results in healthy even in start-period", func() { 197 session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-start-period", "2m", "--health-retries", "2", "--health-cmd", "ls || exit 1", ALPINE, "top"}) 198 session.WaitWithDefaultTimeout() 199 Expect(session).Should(ExitCleanly()) 200 201 hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 202 hc.WaitWithDefaultTimeout() 203 Expect(hc).Should(ExitCleanly()) 204 205 inspect := podmanTest.InspectContainer("hc") 206 Expect(inspect[0].State.Health).To(HaveField("Status", define.HealthCheckHealthy)) 207 }) 208 209 It("podman healthcheck unhealthy but valid arguments check", func() { 210 session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-retries", "2", "--health-cmd", "[\"ls\", \"/foo\"]", ALPINE, "top"}) 211 session.WaitWithDefaultTimeout() 212 Expect(session).Should(ExitCleanly()) 213 214 hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 215 hc.WaitWithDefaultTimeout() 216 Expect(hc).Should(Exit(1)) 217 }) 218 219 // Run this test with and without healthcheck events, even without events 220 // podman inspect and ps should still show accurate healthcheck results. 221 for _, hcEvent := range []bool{true, false} { 222 hcEvent := hcEvent 223 testName := "hc_events=" + strconv.FormatBool(hcEvent) 224 It("podman healthcheck single healthy result changes failed to healthy "+testName, func() { 225 if !hcEvent { 226 path := filepath.Join(podmanTest.TempDir, "containers.conf") 227 err := os.WriteFile(path, []byte("[engine]\nhealthcheck_events=false\n"), 0o644) 228 Expect(err).ToNot(HaveOccurred()) 229 err = os.Setenv("CONTAINERS_CONF_OVERRIDE", path) 230 Expect(err).ToNot(HaveOccurred()) 231 if IsRemote() { 232 podmanTest.StopRemoteService() 233 podmanTest.StartRemoteService() 234 } 235 } 236 237 session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-retries", "2", "--health-cmd", "ls /foo || exit 1", ALPINE, "top"}) 238 session.WaitWithDefaultTimeout() 239 Expect(session).Should(ExitCleanly()) 240 241 hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 242 hc.WaitWithDefaultTimeout() 243 Expect(hc).Should(Exit(1)) 244 245 inspect := podmanTest.InspectContainer("hc") 246 Expect(inspect[0].State.Health).To(HaveField("Status", "starting")) 247 248 hc = podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 249 hc.WaitWithDefaultTimeout() 250 Expect(hc).Should(Exit(1)) 251 252 inspect = podmanTest.InspectContainer("hc") 253 Expect(inspect[0].State.Health).To(HaveField("Status", define.HealthCheckUnhealthy)) 254 255 foo := podmanTest.Podman([]string{"exec", "hc", "touch", "/foo"}) 256 foo.WaitWithDefaultTimeout() 257 Expect(foo).Should(ExitCleanly()) 258 259 hc = podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 260 hc.WaitWithDefaultTimeout() 261 Expect(hc).Should(ExitCleanly()) 262 263 inspect = podmanTest.InspectContainer("hc") 264 Expect(inspect[0].State.Health).To(HaveField("Status", define.HealthCheckHealthy)) 265 266 // Test that events generated have correct status (#19237) 267 events := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "event=health_status", "--since", "1m"}) 268 events.WaitWithDefaultTimeout() 269 Expect(events).Should(ExitCleanly()) 270 if hcEvent { 271 eventsOut := events.OutputToStringArray() 272 Expect(eventsOut).To(HaveLen(3)) 273 Expect(eventsOut[0]).To(ContainSubstring("health_status=starting")) 274 Expect(eventsOut[1]).To(ContainSubstring("health_status=unhealthy")) 275 Expect(eventsOut[2]).To(ContainSubstring("health_status=healthy")) 276 } else { 277 Expect(events.OutputToString()).To(BeEmpty()) 278 } 279 280 // Test podman ps --filter health is working (#11687) 281 ps := podmanTest.Podman([]string{"ps", "--filter", "health=healthy"}) 282 ps.WaitWithDefaultTimeout() 283 Expect(ps).Should(ExitCleanly()) 284 Expect(ps.OutputToStringArray()).To(HaveLen(2)) 285 Expect(ps.OutputToString()).To(ContainSubstring("hc")) 286 }) 287 } 288 289 It("hc logs do not include exec events", func() { 290 session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-cmd", "true", "--health-interval", "5s", "alpine", "sleep", "60"}) 291 session.WaitWithDefaultTimeout() 292 Expect(session).Should(ExitCleanly()) 293 hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) 294 hc.WaitWithDefaultTimeout() 295 Expect(hc).Should(ExitCleanly()) 296 hcLogs := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "container=hc", "--filter", "event=exec_died", "--filter", "event=exec", "--since", "1m"}) 297 hcLogs.WaitWithDefaultTimeout() 298 Expect(hcLogs).Should(ExitCleanly()) 299 hcLogsArray := hcLogs.OutputToStringArray() 300 Expect(hcLogsArray).To(BeEmpty()) 301 }) 302 303 It("stopping and then starting a container with healthcheck cmd", func() { 304 session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-cmd", "[\"ls\", \"/foo\"]", ALPINE, "top"}) 305 session.WaitWithDefaultTimeout() 306 Expect(session).Should(ExitCleanly()) 307 308 podmanTest.StopContainer("hc") 309 310 startAgain := podmanTest.Podman([]string{"start", "hc"}) 311 startAgain.WaitWithDefaultTimeout() 312 Expect(startAgain).Should(ExitCleanly()) 313 Expect(startAgain.OutputToString()).To(Equal("hc")) 314 Expect(startAgain.ErrorToString()).To(Equal("")) 315 }) 316 317 It("Verify default time is used and no utf-8 escapes", func() { 318 cwd, err := os.Getwd() 319 Expect(err).ToNot(HaveOccurred()) 320 321 podmanTest.AddImageToRWStore(ALPINE) 322 // Write target and fake files 323 containerfile := fmt.Sprintf(`FROM %s 324 HEALTHCHECK CMD ls -l / 2>&1`, ALPINE) 325 containerfilePath := filepath.Join(podmanTest.TempDir, "Containerfile") 326 err = os.WriteFile(containerfilePath, []byte(containerfile), 0644) 327 Expect(err).ToNot(HaveOccurred()) 328 defer func() { 329 Expect(os.Chdir(cwd)).To(Succeed()) 330 }() 331 332 // make cwd as context root path 333 Expect(os.Chdir(podmanTest.TempDir)).To(Succeed()) 334 335 session := podmanTest.Podman([]string{"build", "--format", "docker", "-t", "test", "."}) 336 session.WaitWithDefaultTimeout() 337 Expect(session).Should(ExitCleanly()) 338 339 // Check if image inspect contains CMD-SHELL generated by healthcheck. 340 session = podmanTest.Podman([]string{"image", "inspect", "--format", "{{.Config.Healthcheck}}", "test"}) 341 session.WaitWithDefaultTimeout() 342 Expect(session).Should(ExitCleanly()) 343 Expect(session.OutputToString()).To(ContainSubstring("CMD-SHELL")) 344 345 run := podmanTest.Podman([]string{"run", "-dt", "--name", "hctest", "test", "ls"}) 346 run.WaitWithDefaultTimeout() 347 Expect(run).Should(ExitCleanly()) 348 349 inspect := podmanTest.InspectContainer("hctest") 350 // Check to make sure a default time value was added 351 Expect(inspect[0].Config.Healthcheck.Timeout).To(BeNumerically("==", 30000000000)) 352 // Check to make sure a default time interval value was added 353 Expect(inspect[0].Config.Healthcheck.Interval).To(BeNumerically("==", 30000000000)) 354 // Check to make sure characters were not coerced to utf8 355 Expect(inspect[0].Config.Healthcheck).To(HaveField("Test", []string{"CMD-SHELL", "ls -l / 2>&1"})) 356 }) 357 358 It("Startup healthcheck success transitions to regular healthcheck", func() { 359 ctrName := "hcCtr" 360 ctrRun := podmanTest.Podman([]string{"run", "-dt", "--name", ctrName, "--health-cmd", "echo regular", "--health-startup-cmd", "cat /test", ALPINE, "top"}) 361 ctrRun.WaitWithDefaultTimeout() 362 Expect(ctrRun).Should(ExitCleanly()) 363 364 inspect := podmanTest.InspectContainer(ctrName) 365 Expect(inspect[0].State.Health).To(HaveField("Status", "starting")) 366 367 hc := podmanTest.Podman([]string{"healthcheck", "run", ctrName}) 368 hc.WaitWithDefaultTimeout() 369 Expect(hc).Should(Exit(1)) 370 371 exec := podmanTest.Podman([]string{"exec", ctrName, "sh", "-c", "touch /test && echo startup > /test"}) 372 exec.WaitWithDefaultTimeout() 373 Expect(exec).Should(ExitCleanly()) 374 375 hc = podmanTest.Podman([]string{"healthcheck", "run", ctrName}) 376 hc.WaitWithDefaultTimeout() 377 Expect(hc).Should(ExitCleanly()) 378 379 inspect = podmanTest.InspectContainer(ctrName) 380 Expect(inspect[0].State.Health).To(HaveField("Status", define.HealthCheckHealthy)) 381 382 hc = podmanTest.Podman([]string{"healthcheck", "run", ctrName}) 383 hc.WaitWithDefaultTimeout() 384 Expect(hc).Should(ExitCleanly()) 385 386 inspect = podmanTest.InspectContainer(ctrName) 387 Expect(inspect[0].State.Health).To(HaveField("Status", define.HealthCheckHealthy)) 388 389 // Test podman ps --filter health is working (#11687) 390 ps := podmanTest.Podman([]string{"ps", "--filter", "health=healthy"}) 391 ps.WaitWithDefaultTimeout() 392 Expect(ps).Should(ExitCleanly()) 393 Expect(ps.OutputToStringArray()).To(HaveLen(2)) 394 Expect(ps.OutputToString()).To(ContainSubstring("hc")) 395 }) 396 })