From b78dc396ac55543a15d627f0ac68b2d6f84c224c Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 12 Nov 2019 13:36:55 +0100 Subject: [PATCH 1/4] initial version of healthz endpoint --- amqp/amqpclient.go | 9 +++++++ routes/healthz/healthz_endpoint.go | 41 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 routes/healthz/healthz_endpoint.go diff --git a/amqp/amqpclient.go b/amqp/amqpclient.go index 9ab55f6..b70e7da 100644 --- a/amqp/amqpclient.go +++ b/amqp/amqpclient.go @@ -163,3 +163,12 @@ func PingAMQP() error { err := SendActionAMQP(a, "") return err } + +func CheckConnection() error { + + if client.connection.IsClosed() { + return fmt.Errorf("connection to broker is closed") + } + + return nil +} diff --git a/routes/healthz/healthz_endpoint.go b/routes/healthz/healthz_endpoint.go new file mode 100644 index 0000000..d316c85 --- /dev/null +++ b/routes/healthz/healthz_endpoint.go @@ -0,0 +1,41 @@ +package healthz + +import ( + "git.rwth-aachen.de/acs/public/villas/web-backend-go/amqp" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" + "github.com/gin-gonic/gin" + "net/http" +) + +func RegisterHealthzEndpoint(r *gin.RouterGroup) { + + r.GET("", getHealth) +} + +// getHealth godoc +// @Summary Get health status of backend +// @ID getHealth +// @Produce json +// @Tags healthz +// @Success 200 "Backend is healthy, database and AMQP broker connections are alive" +// @Failure 500 "Backend is NOT healthy" +// @Router /healthz [get] +func getHealth(c *gin.Context) { + + // check if DB connection is active + db := database.GetDB() + err := db.DB().Ping() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"message": err}) + } + + // check if connection to AMQP broker is alive if backend was started with AMQP client + if len(database.AMQP_URL) != 0 { + err = amqp.CheckConnection() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"message": err}) + } + } + + c.JSON(http.StatusOK, gin.H{}) +} From 16356688cced7c9f516b322c0b628a7e4f0938cc Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 12 Nov 2019 17:00:37 +0100 Subject: [PATCH 2/4] started implementation of test for healthz endpoint --- routes/healthz/healthz_test.go | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 routes/healthz/healthz_test.go diff --git a/routes/healthz/healthz_test.go b/routes/healthz/healthz_test.go new file mode 100644 index 0000000..cdb106b --- /dev/null +++ b/routes/healthz/healthz_test.go @@ -0,0 +1,52 @@ +package healthz + +import ( + "git.rwth-aachen.de/acs/public/villas/web-backend-go/amqp" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/user" + "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" + "github.com/stretchr/testify/assert" + "net/http" + "testing" +) + +var router *gin.Engine +var db *gorm.DB + +const test_amqp_url = "amqp://villas:villas@rabbitmq:5672" + +//const test_amqp_url = "amqp://rabbit@goofy:5672" + +func TestHealthz(t *testing.T) { + database.AMQP_URL = test_amqp_url + // connect DB + db = database.InitDB(database.DB_NAME, true) + defer db.Close() + + assert.NoError(t, database.DBAddAdminAndUserAndGuest(db)) + + router = gin.Default() + api := router.Group("/api") + + user.RegisterAuthenticate(api.Group("/authenticate")) + api.Use(user.Authentication(true)) + RegisterHealthzEndpoint(api.Group("/healthz")) + + // authenticate as normal user + token, err := helper.AuthenticateForTest(router, + "/api/authenticate", "POST", helper.UserACredentials) + assert.NoError(t, err) + + // connect AMQP client + + err = amqp.ConnectAMQP(database.AMQP_URL) + assert.NoError(t, err) + + // test healthz endpoint for connected DB and AMQP client + code, resp, err := helper.TestEndpoint(router, token, "api/healthz", http.MethodGet, nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + +} From c6cac69906f927f7ecdc01097012877101f466bc Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Wed, 13 Nov 2019 09:43:37 +0100 Subject: [PATCH 3/4] #26 complete implementation of healthz endpoint and testing --- amqp/amqpclient.go | 10 +++++++--- routes/healthz/healthz_endpoint.go | 12 +++++++++--- routes/healthz/healthz_test.go | 30 ++++++++++++++++++++++-------- routes/user/user_middleware.go | 3 +-- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/amqp/amqpclient.go b/amqp/amqpclient.go index b70e7da..de4f6b4 100644 --- a/amqp/amqpclient.go +++ b/amqp/amqpclient.go @@ -35,7 +35,7 @@ func ConnectAMQP(uri string) error { // connect to broker client.connection, err = amqp.Dial(uri) if err != nil { - return fmt.Errorf("AMQP: failed to connect to RabbitMQ broker") + return fmt.Errorf("AMQP: failed to connect to RabbitMQ broker %v", uri) } // create channel @@ -166,8 +166,12 @@ func PingAMQP() error { func CheckConnection() error { - if client.connection.IsClosed() { - return fmt.Errorf("connection to broker is closed") + if client.connection != nil { + if client.connection.IsClosed() { + return fmt.Errorf("connection to broker is closed") + } + } else { + return fmt.Errorf("connection is nil") } return nil diff --git a/routes/healthz/healthz_endpoint.go b/routes/healthz/healthz_endpoint.go index d316c85..c3b3ef5 100644 --- a/routes/healthz/healthz_endpoint.go +++ b/routes/healthz/healthz_endpoint.go @@ -1,6 +1,7 @@ package healthz import ( + "fmt" "git.rwth-aachen.de/acs/public/villas/web-backend-go/amqp" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "github.com/gin-gonic/gin" @@ -18,7 +19,7 @@ func RegisterHealthzEndpoint(r *gin.RouterGroup) { // @Produce json // @Tags healthz // @Success 200 "Backend is healthy, database and AMQP broker connections are alive" -// @Failure 500 "Backend is NOT healthy" +// @Failure 500 {object} docs.ResponseError "Backend is NOT healthy" // @Router /healthz [get] func getHealth(c *gin.Context) { @@ -26,14 +27,19 @@ func getHealth(c *gin.Context) { db := database.GetDB() err := db.DB().Ping() if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err}) + return } // check if connection to AMQP broker is alive if backend was started with AMQP client if len(database.AMQP_URL) != 0 { err = amqp.CheckConnection() if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"message": err}) + fmt.Println(err.Error()) + c.JSON(http.StatusInternalServerError, gin.H{ + "success:": false, + "message": err.Error(), + }) + return } } diff --git a/routes/healthz/healthz_test.go b/routes/healthz/healthz_test.go index cdb106b..f8c78d9 100644 --- a/routes/healthz/healthz_test.go +++ b/routes/healthz/healthz_test.go @@ -2,6 +2,7 @@ package healthz import ( "git.rwth-aachen.de/acs/public/villas/web-backend-go/amqp" + //"git.rwth-aachen.de/acs/public/villas/web-backend-go/amqp" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/user" @@ -9,18 +10,15 @@ import ( "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" "net/http" + + //"net/http" "testing" ) var router *gin.Engine var db *gorm.DB -const test_amqp_url = "amqp://villas:villas@rabbitmq:5672" - -//const test_amqp_url = "amqp://rabbit@goofy:5672" - func TestHealthz(t *testing.T) { - database.AMQP_URL = test_amqp_url // connect DB db = database.InitDB(database.DB_NAME, true) defer db.Close() @@ -39,14 +37,30 @@ func TestHealthz(t *testing.T) { "/api/authenticate", "POST", helper.UserACredentials) assert.NoError(t, err) - // connect AMQP client + // close db connection + err = db.Close() + assert.NoError(t, err) + // test healthz endpoint for unconnected DB and AMQP client + code, resp, err := helper.TestEndpoint(router, token, "api/healthz", http.MethodGet, nil) + assert.NoError(t, err) + assert.Equalf(t, 500, code, "Response body: \n%v\n", resp) + + // reconnect DB + db = database.InitDB(database.DB_NAME, false) + defer db.Close() + + // test healthz endpoint for connected DB and unconnected AMQP client + code, resp, err = helper.TestEndpoint(router, token, "api/healthz", http.MethodGet, nil) + assert.NoError(t, err) + assert.Equalf(t, 500, code, "Response body: \n%v\n", resp) + + // connect AMQP client (make sure that AMQP_URL is set via command line parameter -amqp) err = amqp.ConnectAMQP(database.AMQP_URL) assert.NoError(t, err) // test healthz endpoint for connected DB and AMQP client - code, resp, err := helper.TestEndpoint(router, token, "api/healthz", http.MethodGet, nil) + code, resp, err = helper.TestEndpoint(router, token, "api/healthz", http.MethodGet, nil) assert.NoError(t, err) assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) - } diff --git a/routes/user/user_middleware.go b/routes/user/user_middleware.go index eebf64b..a64eaf0 100644 --- a/routes/user/user_middleware.go +++ b/routes/user/user_middleware.go @@ -14,8 +14,7 @@ func userToContext(ctx *gin.Context, user_id uint) { var user User err := user.ByID(user_id) - if err != nil { - helper.UnauthorizedAbort(ctx, "Authentication failed (user not found)") + if helper.DBError(ctx, err) { return } From 3823858b91368308e4d324c4727e4423569a6f23 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Wed, 13 Nov 2019 09:44:59 +0100 Subject: [PATCH 4/4] CI: add new job to test healthz endpoint --- .gitlab-ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index db5e201..886e74e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -154,6 +154,11 @@ test:user: variables: TEST_FOLDER: routes/user +test:healthz: + extends: test:database + variables: + TEST_FOLDER: routes/healthz + # Stage: deploy ##############################################################################