Browse Source

Merge pull request #896 from docker/aci_health_e2e

Deploy e2e compose stack with health check and validate it restarts failed container
Nicolas De loof 5 years ago
parent
commit
3e0fadc78b

+ 35 - 0
tests/aci-e2e/e2e-aci_test.go

@@ -591,6 +591,41 @@ func TestUpSecretsResources(t *testing.T) {
 		assert.Equal(t, web2Inspect.HostConfig.CPUReservation, 0.5)
 		assert.Equal(t, web2Inspect.HostConfig.CPUReservation, 0.5)
 		assert.Equal(t, web2Inspect.HostConfig.MemoryReservation, uint64(751619276))
 		assert.Equal(t, web2Inspect.HostConfig.MemoryReservation, uint64(751619276))
 	})
 	})
+
+	t.Run("check healthchecks inspect", func(t *testing.T) {
+		assert.Equal(t, web1Inspect.Healthcheck.Disable, false)
+		assert.Equal(t, time.Duration(web1Inspect.Healthcheck.Interval), 5*time.Second)
+		assert.DeepEqual(t, web1Inspect.Healthcheck.Test, []string{"curl", "-f", "http://localhost:80/healthz"})
+
+		assert.Equal(t, web2Inspect.Healthcheck.Disable, true)
+	})
+
+	t.Run("healthcheck restart failed app", func(t *testing.T) {
+		endpoint := fmt.Sprintf("http://%s:%d", web1Inspect.Ports[0].HostIP, web1Inspect.Ports[0].HostPort)
+		HTTPGetWithRetry(t, endpoint+"/failtestserver", http.StatusOK, 3*time.Second, 3*time.Second)
+
+		logs := c.RunDockerCmd("logs", web1).Combined()
+		assert.Assert(t, strings.Contains(logs, "GET /healthz"))
+		assert.Assert(t, strings.Contains(logs, "GET /failtestserver"))
+		assert.Assert(t, strings.Contains(logs, "Server failing"))
+
+		checkLogsReset := func(logt poll.LogT) poll.Result {
+			res := c.RunDockerOrExitError("logs", web1)
+			if res.ExitCode == 0 &&
+				!strings.Contains(res.Combined(), "GET /failtestserver") &&
+				strings.Contains(res.Combined(), "Listening on port 80") &&
+				strings.Contains(res.Combined(), "GET /healthz") {
+				return poll.Success()
+			}
+			return poll.Continue("Logs not reset by healcheck restart\n" + res.Combined())
+		}
+
+		poll.WaitOn(t, checkLogsReset, poll.WithDelay(5*time.Second), poll.WithTimeout(90*time.Second))
+
+		res := c.RunDockerCmd("inspect", web1)
+		web1Inspect, err = ParseContainerInspect(res.Stdout())
+		assert.Equal(t, web1Inspect.Status, "Running")
+	})
 }
 }
 
 
 func TestUpUpdate(t *testing.T) {
 func TestUpUpdate(t *testing.T) {

+ 7 - 0
tests/composefiles/aci_secrets_resources/compose.yml

@@ -9,6 +9,8 @@ services:
         target: mytarget1
         target: mytarget1
       - mysecret2
       - mysecret2
     deploy:
     deploy:
+      restart_policy:
+        condition: on-failure
       resources:
       resources:
         limits:
         limits:
           cpus: '0.7'
           cpus: '0.7'
@@ -16,6 +18,9 @@ services:
         reservations:
         reservations:
           cpus: '0.5'
           cpus: '0.5'
           memory: 0.5G
           memory: 0.5G
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:80/healthz"]
+      interval: 5s
 
 
   web2:
   web2:
     build: ./web
     build: ./web
@@ -25,6 +30,8 @@ services:
     environment:
     environment:
       - PORT=8080
       - PORT=8080
     deploy:
     deploy:
+      restart_policy:
+        condition: on-failure
       resources:
       resources:
         reservations:
         reservations:
           cpus: '0.5'
           cpus: '0.5'

+ 1 - 0
tests/composefiles/aci_secrets_resources/web/Dockerfile

@@ -21,5 +21,6 @@ RUN --mount=type=cache,target=/go/pkg/mod \
     CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o server main.go
     CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o server main.go
 
 
 FROM alpine
 FROM alpine
+RUN apk --no-cache add curl
 COPY --from=build /go/server /
 COPY --from=build /go/server /
 CMD /server "${PORT:-80}" "${DIR:-/run/secrets}"
 CMD /server "${PORT:-80}" "${DIR:-/run/secrets}"

+ 40 - 2
tests/composefiles/aci_secrets_resources/web/main.go

@@ -17,11 +17,49 @@
 package main
 package main
 
 
 import (
 import (
-	"log"
+	"fmt"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
 )
 )
 
 
 func main() {
 func main() {
-	log.Fatal(http.ListenAndServe(":"+os.Args[1], http.FileServer(http.Dir(os.Args[2]))))
+	if len(os.Args) < 2 {
+		fmt.Fprintln(os.Stderr, "Usage: web PORT FOLDER")
+		os.Exit(1)
+	}
+
+	http.HandleFunc("/failtestserver", log(fail))
+	http.HandleFunc("/healthz", log(healthz))
+	dir := os.Args[2]
+	fileServer := http.FileServer(http.Dir(dir))
+	http.HandleFunc("/", log(fileServer.ServeHTTP))
+
+	port := os.Args[1]
+	fmt.Println("Listening on port " + port)
+	err := http.ListenAndServe(":"+port, nil)
+	if err != nil {
+		fmt.Printf("Error while starting server: %v", err)
+	}
+}
+
+var healthy bool = true
+
+func fail(w http.ResponseWriter, req *http.Request) {
+	healthy = false
+	fmt.Println("Server failing")
+}
+
+func healthz(w http.ResponseWriter, r *http.Request) {
+	if !healthy {
+		fmt.Println("unhealthy")
+		w.WriteHeader(http.StatusServiceUnavailable)
+		return
+	}
+}
+
+func log(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
+	return func(w http.ResponseWriter, r *http.Request) {
+		fmt.Println(r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent())
+		handler(w, r)
+	}
 }
 }