diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 6363920..88ba53d 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -1,9 +1,23 @@ -name: Integration testing +name: Unit test suite run env: + # sensu container config SENSU_IMAGE: bitlayer/docker-sensu:1.9.0-1 + TRANSPORT_NAME: rabbitmq + RABBITMQ_HOST: 127.0.0.1 + RABBITMQ_USER: guest + RABBITMQ_PASSWORD: "" + RABBITMQ_VHOST: "/sensu" + REDIS_HOST: 127.0.0.1 + REDIS_PORT: 6379 + # redis container config + REDIS_IMAGE: redis:6.2.1 + # rabbitmq container config RABBITMQ_IMAGE: rabbitmq:3.7.24 + # qdr container config QDROUTERD_IMAGE: quay.io/interconnectedcloud/qdrouterd:1.12.0 + # loki container config LOKI_IMAGE: grafana/loki:2.1.0 + # misc. config COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }} on: push @@ -15,6 +29,9 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 + - name: Start Redis + run: | + docker run --name redis -p 6379:6379 -d $REDIS_IMAGE - name: Start RabbitMQ message bus run: | docker run --name=rabbitmq -p 5672:5672 -p 4369:4369 -d $RABBITMQ_IMAGE @@ -29,18 +46,36 @@ jobs: docker exec rabbitmq rabbitmqctl set_permissions -p "/sensu" guest ".*" ".*" ".*" - name: Start Sensu run: | - docker run --name sensu-core --volume=$PWD/ci/sensu/conf.d:/etc/sensu/conf.d:ro --network host -d $SENSU_IMAGE + docker run --name sensu-api --network host --env-file=$PWD/ci/sensu-env.sh -d $SENSU_IMAGE api + docker run --name sensu-server --network host --env-file=$PWD/ci/sensu-env.sh --volume=$PWD/ci/sensu/check.d:/etc/sensu/check.d:ro -d $SENSU_IMAGE server - name: Start Loki run: | docker run --name loki --volume=$PWD/ci/loki-config.yaml:/etc/loki/loki-config.yaml:ro -p 3100:3100 -d $LOKI_IMAGE -config.file=/etc/loki/loki-config.yaml - - name: List dependency containers + - name: List dependency containers' logs run: | docker ps --all + echo "---- rabbitmq ----" docker logs rabbitmq + echo "---- qdr ----" docker logs qdr - docker logs sensu-core + echo "---- sensu-core ----" + docker logs sensu-server + docker logs sensu-api + echo "---- loki ----" docker logs loki - - name: Run integration tests + - name: Run unit tests run: | export PROJECT_ROOT=/root/go/src/github.com/infrawatch/apputils docker run -uroot --network host --volume=$PWD:$PROJECT_ROOT:z --workdir $PROJECT_ROOT centos:8 bash ci/run_ci.sh + - name: List dependency containers' logs + run: | + echo "---- rabbitmq ----" + docker logs rabbitmq + echo "---- qdr ----" + docker logs qdr + echo "---- sensu-core ----" + docker logs sensu-server + docker logs sensu-api + echo "---- loki ----" + docker logs loki + if: ${{ failure() }} diff --git a/ci/sensu-env.sh b/ci/sensu-env.sh new file mode 100644 index 0000000..324a05a --- /dev/null +++ b/ci/sensu-env.sh @@ -0,0 +1,7 @@ +TRANSPORT_NAME=rabbitmq +RABBITMQ_HOST=127.0.0.1 +RABBITMQ_USER=guest +RABBITMQ_PASSWORD=guest +RABBITMQ_VHOST=/sensu +REDIS_HOST=127.0.0.1 +REDIS_PORT=6379 diff --git a/ci/sensu/check.d/test.json b/ci/sensu/check.d/test.json new file mode 100644 index 0000000..435b8b6 --- /dev/null +++ b/ci/sensu/check.d/test.json @@ -0,0 +1,14 @@ +{ + "checks": { + "echo": { + "command": "echo \"wubba lubba\" && exit 1", + "interval": 1, + "subscribers": [ + "ci" + ], + "handlers": [ + "handle-ci" + ] + } + } +} diff --git a/ci/sensu/handlers/test.json b/ci/sensu/handlers/test.json new file mode 100644 index 0000000..01263df --- /dev/null +++ b/ci/sensu/handlers/test.json @@ -0,0 +1,8 @@ +{ + "handlers": { + "handle-ci": { + "type": "pipe", + "command": "cat >/tmp/apputils-sensu-result-received.txt" + } + } +} diff --git a/connector/sensu.go b/connector/sensu.go index d040b80..9a9848a 100644 --- a/connector/sensu.go +++ b/connector/sensu.go @@ -22,13 +22,15 @@ const ( //Result contains data about check execution type Result struct { - Command string `json:"command"` - Name string `json:"name"` - Issued int64 `json:"issued"` - Executed int64 `json:"executed"` - Duration float64 `json:"duration"` - Output string `json:"output"` - Status int `json:"status"` + Command string `json:"command"` + Name string `json:"name"` + Issued int64 `json:"issued"` + Handlers []string `json:"handlers,omitempty"` + Handler string `json:"handler,omitempty"` + Executed int64 `json:"executed"` + Duration float64 `json:"duration"` + Output string `json:"output"` + Status int `json:"status"` } //CheckResult represents message structure for sending check results back to Sensu server @@ -39,9 +41,11 @@ type CheckResult struct { //CheckRequest is the output of the connector's listening loop type CheckRequest struct { - Command string `json:"command"` - Name string `json:"name"` - Issued int64 `json:"issued"` + Command string `json:"command"` + Name string `json:"name"` + Issued int64 `json:"issued"` + Handlers []string `json:"handlers,omitempty"` + Handler string `json:"handler,omitempty"` } //Keepalive holds structure for Sensu KeepAlive messages diff --git a/tests/connector_test.go b/tests/connector_test.go index d494303..724f612 100644 --- a/tests/connector_test.go +++ b/tests/connector_test.go @@ -1,8 +1,10 @@ package tests import ( + "encoding/json" "io/ioutil" "log" + "net/http" "os" "path" "strconv" @@ -435,3 +437,79 @@ func TestLoki(t *testing.T) { } }) } + +func TestSensuCommunication(t *testing.T) { + tmpdir, err := ioutil.TempDir(".", "connector_test_tmp") + if err != nil { + log.Fatal(err) + } + defer os.RemoveAll(tmpdir) + logpath := path.Join(tmpdir, "test.log") + logger, err := logging.NewLogger(logging.DEBUG, logpath) + if err != nil { + t.Fatalf("Failed to open log file %s. %s\n", logpath, err) + } + defer logger.Destroy() + + // check for "ci" subscribers is defined in ci/sensu/check.d/test.json + sensu, err := connector.CreateSensuConnector(logger, "amqp://127.0.0.1:5672//sensu", "ci-unit", "127.0.0.1", 1, []string{"ci"}) + assert.NoError(t, err) + + t.Run("Test communication with sensu-core server", func(t *testing.T) { + requests := make(chan interface{}) + results := make(chan interface{}) + sensu.Start(requests, results) + + // wait for request from sensu-core server + for req := range requests { + switch reqst := req.(type) { + case connector.CheckRequest: + // verify we received awaited check request + assert.Equal(t, "echo", reqst.Name) + assert.Equal(t, "echo \"wubba lubba\" && exit 1", reqst.Command) + + // mock result and send it + result := connector.CheckResult{ + Client: sensu.ClientName, + Result: connector.Result{ + Command: reqst.Command, + Name: reqst.Name, + Issued: reqst.Issued, + Handlers: reqst.Handlers, + Handler: reqst.Handler, + Executed: time.Now().Unix(), + Duration: time.Millisecond.Seconds(), + Output: "wubba lubba", + Status: 1, + }, + } + results <- result + goto done + } + } + done: + // wait for sensu handler to create result receive verification file + time.Sleep(time.Second) + + resp, err := http.Get("http://127.0.0.1:4567/results") + assert.NoError(t, err) + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + assert.NoError(t, err) + + var resultList []connector.CheckResult + err = json.Unmarshal(body, &resultList) + assert.NoError(t, err) + found := false + for _, res := range resultList { + if res.Client == "ci-unit" { + if res.Result.Name == "echo" && res.Result.Command == "echo \"wubba lubba\" && exit 1" { + found = true + break + } + } + } + assert.True(t, found) + }) +}