From 53668647c8d3a8dc0646e710edbe9ae069a96bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Carranza=20V=C3=A9lez?= Date: Tue, 28 Jul 2015 21:10:17 +0000 Subject: [PATCH] Integration test is now working. * More work on the integration test * Correctly get supervisor IP * Use Fatal for test errors * test-integration working separate from run-supervisor * Use jenkins' JOB_NAME to identify and remove containers with their volumes * Document testing procedure * Document the assume-unchanged tip * Use /mnt/root for data path * Nicer secret assignment --- Makefile | 17 +++- README.md | 22 +++++ automation/jenkins_build.sh | 6 +- entry.sh | 2 +- gosuper/main.go | 4 +- gosuper/main_test.go | 7 +- src/api.coffee | 2 +- src/app.coffee | 3 +- supertest/super_test.go | 96 +++++++++++++++++++ supertest/supertest.go | 32 ------- tools/dind/Dockerfile | 2 + .../services/resin-supervisor-dind.service | 1 + 12 files changed, 149 insertions(+), 45 deletions(-) create mode 100644 supertest/super_test.go delete mode 100644 supertest/supertest.go diff --git a/Makefile b/Makefile index 5fa887bb..232674cb 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ ARCH = rpi# rpi/amd64/i386/armv7hf DEPLOY_REGISTRY = registry.resindev.io/ SUPERVISOR_VERSION = master +JOB_NAME = 1 all: supervisor @@ -51,16 +52,24 @@ deploy: supervisor docker push $(DEPLOY_REGISTRY)$(IMAGE) go-builder: + -cp tools/dind/config.json . docker build -f Dockerfile.gosuper -t resin/go-supervisor-builder:$(SUPERVISOR_VERSION) . + -rm config.json gosuper: go-builder -mkdir -p gosuper/bin - docker run --rm -v $(shell pwd)/gosuper/bin:/usr/src/app/bin -e USER_ID=$(shell id -u) -e GROUP_ID=$(shell id -g) -e GOARCH=$(GOARCH) resin/go-supervisor-builder:$(SUPERVISOR_VERSION) + -docker rm --volumes -f resin_build_gosuper_$(JOB_NAME) + docker run --name resin_build_gosuper_$(JOB_NAME) -v $(shell pwd)/gosuper/bin:/usr/src/app/bin -e USER_ID=$(shell id -u) -e GROUP_ID=$(shell id -g) -e GOARCH=$(GOARCH) resin/go-supervisor-builder:$(SUPERVISOR_VERSION) + docker rm --volumes -f resin_build_gosuper_$(JOB_NAME) test-gosuper: go-builder - docker run --rm -v $(shell pwd)/gosuper/bin:/usr/src/app/bin resin/go-supervisor-builder:$(SUPERVISOR_VERSION) bash -c "cd src/resin-supervisor && go test -v ./gosuper" + -docker rm --volumes -f resin_test_gosuper_$(JOB_NAME) + docker run --name resin_test_gosuper_$(JOB_NAME) -v $(shell pwd)/gosuper/bin:/usr/src/app/bin resin/go-supervisor-builder:$(SUPERVISOR_VERSION) bash -c "cd src/resin-supervisor && go test -v ./gosuper" + docker rm --volumes -f resin_test_gosuper_$(JOB_NAME) -test-integration: run-supervisor go-builder - docker run --rm --net=host -e SUPERVISOR_IP="$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' resin_supervisor_1)" -v $(shell pwd)/gosuper/bin:/usr/src/app/bin resin/go-supervisor-builder:$(SUPERVISOR_VERSION) bash -c "cd src/resin-supervisor && go test -v ./supertest" +test-integration: go-builder + -docker rm --volumes -f resin_test_integration_$(JOB_NAME) + docker run --name resin_test_integration_$(JOB_NAME) --net=host -e SUPERVISOR_IP="$(shell docker inspect --format '{{ .NetworkSettings.IPAddress }}' resin_supervisor_1)" -v $(shell pwd)/gosuper/bin:/usr/src/app/bin --volumes-from resin_supervisor_1 resin/go-supervisor-builder:$(SUPERVISOR_VERSION) bash -c "cd src/resin-supervisor && go test -v ./supertest" + docker rm --volumes -f resin_test_integration_$(JOB_NAME) .PHONY: supervisor deploy supervisor-dind run-supervisor diff --git a/README.md b/README.md index 66507eb6..b8034259 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,11 @@ This file can be obtained in several ways, for instance: * Download an Intel Edison image from staging, open `config.img` with an archive tool like [peazip](http://sourceforge.net/projects/peazip/files/) * Download a Raspberry Pi 2 image, flash it to an SD card, then mount partition 5 (resin-conf). +Tip: to avoid git marking config.json as modified, you can run: +```bash +git update-index --assume-unchanged tools/dind/config.json +``` + ## Start the supervisor instance ```bash make ARCH=amd64 run-supervisor @@ -78,3 +83,20 @@ And we save it to Godeps.json with: godep save -r ./... ``` (The -r switch will modify the import statement to use Godep's `_workspace`) + +## Testing +# Gosuper +The Go supervisor can be tested by running: +```bash +make ARCH=amd64 test-gosuper +``` +The test suite is at [gosuper/main_test.go](./gosuper/main_test.go). +# Integration test +The integration test tests the supervisor API by hitting its endpoints. To run it, first run the supervisor as explained in the first section of this document. + +Once it's running, you can run the test with: +```bash +make ARCH=amd64 test-integration +``` +The tests will fail if the supervisor API is down - bear in mind that the supervisor image takes a while to start the actual supervisor program, so you might have to wait a few minutes between running the supervisor and testing it. +The test expects the supervisor to be already running the application (so that the app is already on the SQLite database), so check the dashboard to see if the app has already downloaded. diff --git a/automation/jenkins_build.sh b/automation/jenkins_build.sh index f9321ff9..4a4cd0c3 100755 --- a/automation/jenkins_build.sh +++ b/automation/jenkins_build.sh @@ -9,8 +9,8 @@ ESCAPED_BRANCH_NAME=$(echo $sourceBranch | sed 's/[^a-z0-9A-Z_.-]/-/g') docker pull resin/${ARCH}-supervisor:${ESCAPED_BRANCH_NAME} || docker pull resin/${ARCH}-supervisor:master || true # Test the gosuper -make SUPERVISOR_VERSION=${VERSION} test-gosuper +make SUPERVISOR_VERSION=${VERSION} JOB_NAME=${JOB_NAME} test-gosuper # Build the images -make SUPERVISOR_VERSION=${ESCAPED_BRANCH_NAME} ARCH=${ARCH} DEPLOY_REGISTRY= deploy -make SUPERVISOR_VERSION=${VERSION} ARCH=${ARCH} DEPLOY_REGISTRY= deploy +make SUPERVISOR_VERSION=${ESCAPED_BRANCH_NAME} ARCH=${ARCH} JOB_NAME=${JOB_NAME} DEPLOY_REGISTRY= deploy +make SUPERVISOR_VERSION=${VERSION} ARCH=${ARCH} JOB_NAME=${JOB_NAME} DEPLOY_REGISTRY= deploy diff --git a/entry.sh b/entry.sh index 33fefec6..545a8755 100755 --- a/entry.sh +++ b/entry.sh @@ -3,7 +3,7 @@ set -e if [ -z "$GOSUPER_SOCKET" ]; then - GOSUPER_SOCKET=/var/run/gosuper.sock + export GOSUPER_SOCKET=/var/run/gosuper.sock fi [ -d /dev/net ] || diff --git a/gosuper/main.go b/gosuper/main.go index b3f9d1ee..bf35307b 100644 --- a/gosuper/main.go +++ b/gosuper/main.go @@ -27,7 +27,7 @@ func main() { } */ - ResinDataPath = "/resin-data/" + ResinDataPath = "/mnt/root/resin-data/" listenAddress := os.Getenv("GOSUPER_SOCKET") r := mux.NewRouter() @@ -36,10 +36,12 @@ func main() { apiv1.HandleFunc("/purge", PurgeHandler).Methods("POST") + fmt.Println("Going to listen on " + listenAddress) listener, err := net.Listen("unix", listenAddress) if err != nil { log.Fatalf("Could not listen on "+listenAddress+": %v", err) } + fmt.Println("Starting HTTP server") err = http.Serve(listener, r) if err != nil { log.Fatalf("Could not start HTTP server: %v", err) diff --git a/gosuper/main_test.go b/gosuper/main_test.go index 899448e9..a8604c4c 100644 --- a/gosuper/main_test.go +++ b/gosuper/main_test.go @@ -11,7 +11,7 @@ import ( func TestPurge(t *testing.T) { appId := "1" - req, _ := http.NewRequest("POST", "/api/v1/purge", strings.NewReader(`{"applicationId": "`+appId+`"}`)) + req, _ := http.NewRequest("POST", "/v1/purge", strings.NewReader(`{"applicationId": "`+appId+`"}`)) req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") w := httptest.NewRecorder() @@ -25,6 +25,11 @@ func TestPurge(t *testing.T) { t.Error("Could not create test directory for purge") } + err = ioutil.WriteFile(dataPath+"/test", []byte("test"), 777) + if err != nil { + t.Error("Could not create test file for purge") + } + PurgeHandler(w, req) if w.Code != http.StatusOK { diff --git a/src/api.coffee b/src/api.coffee index 84b1dfaf..ca2ba040 100644 --- a/src/api.coffee +++ b/src/api.coffee @@ -73,7 +73,7 @@ module.exports = (secret) -> throw new Error('App not found') application.kill(app) .then -> - request.post(config.gosuperAddress + '/v1/purge', {json: true, body: applicationId: appId}).on 'response', -> + request.post config.gosuperAddress + '/v1/purge', {json: true, body: applicationId: appId}, -> application.start(app) .pipe(res) diff --git a/src/app.coffee b/src/app.coffee index d49a58a2..3f465797 100644 --- a/src/app.coffee +++ b/src/app.coffee @@ -36,8 +36,7 @@ knex.init.then -> console.log('Starting API server..') - secret = randomstring.generate() - secret = config.forceApiSecret if config.forceApiSecret? + secret = config.forceApiSecret ? randomstring.generate() api(secret).listen(config.listenPort) # Let API know what version we are, and our api connection info. diff --git a/supertest/super_test.go b/supertest/super_test.go new file mode 100644 index 00000000..0c505e0c --- /dev/null +++ b/supertest/super_test.go @@ -0,0 +1,96 @@ +package supertest + +import ( + "io/ioutil" + "net/http" + "os" + "strings" + "testing" + + gosuper "resin-supervisor/gosuper" +) + +func TestPing(t *testing.T) { + supervisorIP := os.Getenv("SUPERVISOR_IP") + if supervisorIP == "" { + t.Fatal("Supervisor IP not set - is it running?") + } + + address := "http://" + supervisorIP + ":48484" + + request, err := http.NewRequest("GET", address+"/ping?apikey=bananas", nil) + + if err != nil { + t.Fatal(err) + } + + res, err := http.DefaultClient.Do(request) + + if err != nil { + t.Fatal(err) + } + + if res.StatusCode != 200 { + t.Fatalf("Expected 200, got %d", res.StatusCode) + } +} + +func TestPurge(t *testing.T) { + supervisorIP := os.Getenv("SUPERVISOR_IP") + if supervisorIP == "" { + t.Fatal("Supervisor IP not set - is it running?") + } + + address := "http://" + supervisorIP + ":48484" + + gopath := os.Getenv("GOPATH") + + config, err := gosuper.ReadConfig(gopath + "/src/resin-supervisor/config.json") + if err != nil { + t.Fatal(err) + } + + appId := config.ApplicationId + + dataPath := "/resin-data/" + appId + err = ioutil.WriteFile(dataPath+"/test", []byte("test"), 777) + if err != nil { + t.Error("Could not create test file for purge") + } + + request, err := http.NewRequest("POST", address+"/v1/purge?apikey=bananas", strings.NewReader(`{"appId": "`+appId+`"}`)) + request.Header.Set("Content-Type", "application/json") + + if err != nil { + t.Fatal(err) + } + + client := &http.Client{} + res, err := client.Do(request) + + if err != nil { + t.Fatal(err) + } + + if res.StatusCode != 200 { + t.Fatalf("Expected 200, got %d", res.StatusCode) + } + + defer res.Body.Close() + contents, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + + if !strings.EqualFold(string(contents), `{"Status":"OK","Error":""}`) { + t.Errorf("Purge response didn't match the expected JSON, got: %s", contents) + } + + dirContents, err := ioutil.ReadDir(dataPath) + if err != nil { + t.Errorf("Could not read the data path after purge: %s", err) + } + if len(dirContents) > 0 { + t.Error("Data directory not empty after purge") + } +} diff --git a/supertest/supertest.go b/supertest/supertest.go deleted file mode 100644 index 78403e10..00000000 --- a/supertest/supertest.go +++ /dev/null @@ -1,32 +0,0 @@ -package supertest - -import ( - "net/http" - "os" - "testing" -) - -func TestPing(t *testing.T) { - supervisorIP := os.getEnv("SUPERVISOR_IP") - if supervisorIP == "" { - t.Error("Supervisor IP not set") - } - - address := "http://"+ supervisorIP +":48484" - - request, err := http.NewRequest("GET", address+"/ping?apikey=bananas", nil) - - if err != nil { - t.Error(err) - } - - res, err := http.DefaultClient.Do(request) - - if err != nil { - t.Error(err) - } - - if res.StatusCode != 200 { - t.Errorf("Expected 200, got %d", res.StatusCode) - } -} diff --git a/tools/dind/Dockerfile b/tools/dind/Dockerfile index 8713456b..808900df 100644 --- a/tools/dind/Dockerfile +++ b/tools/dind/Dockerfile @@ -1,6 +1,8 @@ FROM resin/resin-base:1acfee1 VOLUME /var/lib/docker +VOLUME /resin-data + ENV DOCKER_VERSION 1.6.2 # From get.docker.com script diff --git a/tools/dind/config/services/resin-supervisor-dind.service b/tools/dind/config/services/resin-supervisor-dind.service index 4ed998b2..80f62b5c 100644 --- a/tools/dind/config/services/resin-supervisor-dind.service +++ b/tools/dind/config/services/resin-supervisor-dind.service @@ -28,6 +28,7 @@ ExecStart=/usr/bin/docker run --rm --privileged --name resin_supervisor \ -e "LED_FILE=${LED_FILE}" \ -e "LISTEN_PORT=${LISTEN_PORT}" \ -e "SUPERVISOR_IMAGE=${SUPERVISOR_IMAGE}" \ + -e "RESIN_SUPERVISOR_SECRET=${RESIN_SUPERVISOR_SECRET}" \ ${SUPERVISOR_IMAGE} TimeoutStartSec=0 Restart=always