From 05fe1d1fa6cb8ad1987f25f90764fec4a9770de2 Mon Sep 17 00:00:00 2001 From: Peter Bourgon Date: Mon, 28 Sep 2015 12:25:44 +0200 Subject: [PATCH 1/3] zombietest: build container --- zombietest/build/Dockerfile | 9 +++++++++ zombietest/build/Makefile | 9 +++++++++ zombietest/build/zombie.c | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 zombietest/build/Dockerfile create mode 100644 zombietest/build/Makefile create mode 100644 zombietest/build/zombie.c diff --git a/zombietest/build/Dockerfile b/zombietest/build/Dockerfile new file mode 100644 index 0000000..6672cf8 --- /dev/null +++ b/zombietest/build/Dockerfile @@ -0,0 +1,9 @@ +FROM golang:1.5.1 + +COPY ../../*.go /go/src/github.com/peterbourgon/runsvinit +RUN go install -v github.com/peterbourgon/runsvinit +RUN mv /go/bin/runsvinit /mount/ + +COPY zombie.c / +RUN cc -Wall -Werror -o /zombie /zombie.c +RUN mv /zombie /mount/ diff --git a/zombietest/build/Makefile b/zombietest/build/Makefile new file mode 100644 index 0000000..25878cd --- /dev/null +++ b/zombietest/build/Makefile @@ -0,0 +1,9 @@ +.PHONY: all +all: zombie runsvinit + +zombie runsvinit: .uptodate + docker run -ti --rm -v $(shell pwd):/mount zombie-build + +.uptodate: Dockerfile ../../*.go + docker build -t zombie-build . + touch $@ diff --git a/zombietest/build/zombie.c b/zombietest/build/zombie.c new file mode 100644 index 0000000..631333b --- /dev/null +++ b/zombietest/build/zombie.c @@ -0,0 +1,18 @@ +#include +#include +#include + +int main () +{ + pid_t child_pid; + + child_pid = fork (); + if (child_pid > 0) { + sleep (60); + } + else { + exit (0); + } + return 0; +} + From 819c47186ca0418fd78afd2d94599af4108151a7 Mon Sep 17 00:00:00 2001 From: Peter Bourgon Date: Mon, 28 Sep 2015 22:30:44 +0200 Subject: [PATCH 2/3] Working test --- .gitignore | 3 +++ main.go | 45 +++++++++++++++++++++++-------------- zombietest/Dockerfile | 11 +++++++++ zombietest/Makefile | 23 +++++++++++++++++++ zombietest/README.md | 24 ++++++++++++++++++++ zombietest/build/Dockerfile | 10 ++------- zombietest/build/Makefile | 9 -------- zombietest/build/zombie.c | 28 +++++++++++------------ zombietest/run-zombie | 3 +++ zombietest/test.bash | 34 ++++++++++++++++++++++++++++ 10 files changed, 142 insertions(+), 48 deletions(-) create mode 100644 zombietest/Dockerfile create mode 100644 zombietest/Makefile create mode 100644 zombietest/README.md delete mode 100644 zombietest/build/Makefile create mode 100755 zombietest/run-zombie create mode 100755 zombietest/test.bash diff --git a/.gitignore b/.gitignore index 552a1b1..02812de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ runsvinit runsvinit-*-* examples/runsvinit-linux-amd64* +zombietest/zombie +zombietest/runsvinit +*.uptodate # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o diff --git a/main.go b/main.go index 93b8af1..82f1dcc 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "flag" "log" "os" "os/exec" @@ -13,6 +14,9 @@ import ( const etcService = "/etc/service" func main() { + reap := flag.Bool("reap", true, "reap orphan children") + flag.Parse() + log.SetFlags(0) runsvdir, err := exec.LookPath("runsvdir") @@ -35,7 +39,12 @@ func main() { log.Printf("warning: I'm not PID 1, I'm PID %d", pid) } - go reapAll() + if *reap { + log.Print("reaping zombies") + go reapLoop() + } else { + log.Print("NOT reaping zombies") + } supervisor := cmd(runsvdir, etcService) if err := supervisor.Start(); err != nil { @@ -53,31 +62,33 @@ func main() { } } -func reapAll() { +// From https://github.com/ramr/go-reaper/blob/master/reaper.go +func reapLoop() { c := make(chan os.Signal) signal.Notify(c, syscall.SIGCHLD) for range c { - go reapOne() + reapChildren() } } -// From https://github.com/ramr/go-reaper/blob/master/reaper.go -func reapOne() { - var ( - ws syscall.WaitStatus - pid int - err error - ) +func reapChildren() { for { - pid, err = syscall.Wait4(-1, &ws, 0, nil) - if err != syscall.EINTR { - break + var ( + ws syscall.WaitStatus + pid int + err error + ) + for { + pid, err = syscall.Wait4(-1, &ws, 0, nil) + if err != syscall.EINTR { + break + } } + if err == syscall.ECHILD { + return // done + } + log.Printf("reaped child process %d (%+v)", pid, ws) } - if err == syscall.ECHILD { - return - } - log.Printf("reaped child process %d (%+v)", pid, ws) } type signaler interface { diff --git a/zombietest/Dockerfile b/zombietest/Dockerfile new file mode 100644 index 0000000..3012c1a --- /dev/null +++ b/zombietest/Dockerfile @@ -0,0 +1,11 @@ +FROM alpine:latest +RUN echo "http://dl-4.alpinelinux.org/alpine/edge/testing" >>/etc/apk/repositories && \ + apk add --update runit && \ + rm -rf /var/cache/apk/* + +COPY zombie / +RUN mkdir -p /etc/service/zombie +COPY run-zombie /etc/service/zombie/run + +COPY /runsvinit / + diff --git a/zombietest/Makefile b/zombietest/Makefile new file mode 100644 index 0000000..757398d --- /dev/null +++ b/zombietest/Makefile @@ -0,0 +1,23 @@ +.PHONY: test clean + +test: .test.uptodate + ./test.bash + +.test.uptodate: runsvinit zombie run-zombie Dockerfile + docker build -t zombietest . + touch $@ + +runsvinit: ../*.go + env GOOS=linux GOARCH=amd64 go build -o $@ github.com/peterbourgon/runsvinit + +zombie: .build.uptodate + docker run --rm -v $(shell pwd):/mount zombietest-build cc -Wall -Werror -o /mount/zombie /zombie.c + +.build.uptodate: build/zombie.c build/Dockerfile + docker build -t zombietest-build build/ + touch $@ + +clean: + rm -rf .test.uptodate .build.uptodate runsvinit zombie + docker stop zombietest zombietest-build >/dev/null 2>&1 || true + docker rm zombietest zombietest-build >/dev/null 2>&1 || true diff --git a/zombietest/README.md b/zombietest/README.md new file mode 100644 index 0000000..c2a1067 --- /dev/null +++ b/zombietest/README.md @@ -0,0 +1,24 @@ +# zombietest + +This directory contains an integration test to prove runsvinit is actually +reaping zombies. `make` builds and executes the test as follows: + +1. We produce a linux/amd64 runsvinit binary by setting GOOS/GOARCH and + invoking the Go compiler. Requires Go 1.5, or Go 1.4 built with the + appropriate cross-compile options. + +2. The build/zombie.c program spawns five zombies and exits. We compile it for + linux/amd64 via a zombietest-build container. We do this so `make` works + from a Mac. This requires a working Docker installation. + +3. Once we have linux/amd64 runsvinit and zombie binaries, we produce a + zombietest container via the Dockerfile. That container contains a single + runit service, /etc/service/zombie, which supervises the zombie binary. We + provide no default ENTRYPOINT, so we can supply it at runtime. + +4. Once the zombietest container is built, we invoke the test.bash script. + That launches a version of the container with runsvinit set to NOT reap + zombies, and after 1 second, verifies that zombies exist. Then, it launches + a version of the container with runsvinit set to reap zombies, and after 1 + second, verifies that no zombies exist. + diff --git a/zombietest/build/Dockerfile b/zombietest/build/Dockerfile index 6672cf8..2828f5e 100644 --- a/zombietest/build/Dockerfile +++ b/zombietest/build/Dockerfile @@ -1,9 +1,3 @@ -FROM golang:1.5.1 - -COPY ../../*.go /go/src/github.com/peterbourgon/runsvinit -RUN go install -v github.com/peterbourgon/runsvinit -RUN mv /go/bin/runsvinit /mount/ - +FROM alpine:latest +RUN apk add --update gcc musl-dev && rm -rf /var/cache/apk/* COPY zombie.c / -RUN cc -Wall -Werror -o /zombie /zombie.c -RUN mv /zombie /mount/ diff --git a/zombietest/build/Makefile b/zombietest/build/Makefile deleted file mode 100644 index 25878cd..0000000 --- a/zombietest/build/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -.PHONY: all -all: zombie runsvinit - -zombie runsvinit: .uptodate - docker run -ti --rm -v $(shell pwd):/mount zombie-build - -.uptodate: Dockerfile ../../*.go - docker build -t zombie-build . - touch $@ diff --git a/zombietest/build/zombie.c b/zombietest/build/zombie.c index 631333b..0859123 100644 --- a/zombietest/build/zombie.c +++ b/zombietest/build/zombie.c @@ -1,18 +1,18 @@ #include -#include +#include #include -int main () -{ - pid_t child_pid; - - child_pid = fork (); - if (child_pid > 0) { - sleep (60); - } - else { - exit (0); - } - return 0; +int main() { + pid_t pid; + int i; + for (i = 0; i<5; i++) { + pid = fork(); + if (pid > 0) { + printf("Zombie #%d born\n", i + 1); + } else { + printf("Brains...\n"); + exit(0); + } + } + return 0; } - diff --git a/zombietest/run-zombie b/zombietest/run-zombie new file mode 100755 index 0000000..198fbc2 --- /dev/null +++ b/zombietest/run-zombie @@ -0,0 +1,3 @@ +#!/bin/sh + +exec /zombie diff --git a/zombietest/test.bash b/zombietest/test.bash new file mode 100755 index 0000000..ca04214 --- /dev/null +++ b/zombietest/test.bash @@ -0,0 +1,34 @@ +#!/bin/bash + +RC=0 +SLEEP=1 + +C=$(docker run -d zombietest /runsvinit -reap=false) +sleep $SLEEP +NOREAP=$(docker exec $C ps -o pid,stat | grep Z | wc -l) +echo -n without reaping, we have $NOREAP zombies... +if [ "$NOREAP" -le "0" ] +then + echo " FAIL" + RC=1 +else + echo " good" +fi +docker stop $C >/dev/null +docker rm $C >/dev/null + +C=$(docker run -d zombietest /runsvinit) +sleep $SLEEP +YESREAP=$(docker exec $C ps -o pid,stat | grep Z | wc -l) +echo -n with reaping, we have $YESREAP zombies... +if [ "$YESREAP" -gt "0" ] +then + echo " FAIL" + RC=1 +else + echo " good" +fi +docker stop $C >/dev/null +docker rm $C >/dev/null + +exit $RC From f197aa8f6a020649dc8091a2ffda1b5ce1f94aae Mon Sep 17 00:00:00 2001 From: Peter Bourgon Date: Mon, 28 Sep 2015 22:46:18 +0200 Subject: [PATCH 3/3] Try Circle --- circle.yml | 8 ++++++++ zombietest/Makefile | 16 ++++++++++------ zombietest/test.bash | 27 ++++++++++++++++++++------- 3 files changed, 38 insertions(+), 13 deletions(-) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..97109ef --- /dev/null +++ b/circle.yml @@ -0,0 +1,8 @@ +machine: + services: + - docker + +test: + override: + - go test -v + - make CIRCLECI=true SUDO=sudo RM= -C zombietest diff --git a/zombietest/Makefile b/zombietest/Makefile index 757398d..c1d9b19 100644 --- a/zombietest/Makefile +++ b/zombietest/Makefile @@ -1,23 +1,27 @@ +GO?=go +SUDO?= +RM?=--rm + .PHONY: test clean test: .test.uptodate ./test.bash .test.uptodate: runsvinit zombie run-zombie Dockerfile - docker build -t zombietest . + $(SUDO) docker build -t zombietest . touch $@ runsvinit: ../*.go - env GOOS=linux GOARCH=amd64 go build -o $@ github.com/peterbourgon/runsvinit + env GOOS=linux GOARCH=amd64 $(GO) build -o $@ github.com/peterbourgon/runsvinit zombie: .build.uptodate - docker run --rm -v $(shell pwd):/mount zombietest-build cc -Wall -Werror -o /mount/zombie /zombie.c + $(SUDO) docker run $(RM) -v $(shell pwd):/mount zombietest-build cc -Wall -Werror -o /mount/zombie /zombie.c .build.uptodate: build/zombie.c build/Dockerfile - docker build -t zombietest-build build/ + $(SUDO) docker build -t zombietest-build build/ touch $@ clean: rm -rf .test.uptodate .build.uptodate runsvinit zombie - docker stop zombietest zombietest-build >/dev/null 2>&1 || true - docker rm zombietest zombietest-build >/dev/null 2>&1 || true + $(SUDO) docker stop zombietest zombietest-build >/dev/null 2>&1 || true + $(SUDO) docker rm zombietest zombietest-build >/dev/null 2>&1 || true diff --git a/zombietest/test.bash b/zombietest/test.bash index ca04214..f6339d8 100755 --- a/zombietest/test.bash +++ b/zombietest/test.bash @@ -1,11 +1,26 @@ #!/bin/bash -RC=0 +function zombies() { + if [ -z "$CIRCLECI" ] + then + docker exec $C ps -o pid,stat | grep Z | wc -l + else + # https://circleci.com/docs/docker#docker-exec + sudo lxc-attach -n "$(docker inspect --format '{{.Id}}' $C)" -- sh -c "ps -o pid,stat | grep Z | wc -l" + fi +} + +function stop_rm() { + docker stop $1 + docker rm $1 +} + SLEEP=1 +RC=0 C=$(docker run -d zombietest /runsvinit -reap=false) sleep $SLEEP -NOREAP=$(docker exec $C ps -o pid,stat | grep Z | wc -l) +NOREAP=$(zombies) echo -n without reaping, we have $NOREAP zombies... if [ "$NOREAP" -le "0" ] then @@ -14,12 +29,11 @@ then else echo " good" fi -docker stop $C >/dev/null -docker rm $C >/dev/null +stop_rm $C C=$(docker run -d zombietest /runsvinit) sleep $SLEEP -YESREAP=$(docker exec $C ps -o pid,stat | grep Z | wc -l) +YESREAP=$(zombies) echo -n with reaping, we have $YESREAP zombies... if [ "$YESREAP" -gt "0" ] then @@ -28,7 +42,6 @@ then else echo " good" fi -docker stop $C >/dev/null -docker rm $C >/dev/null +stop_rm $C exit $RC