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  })