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 ############################################################################## diff --git a/amqp/amqpclient.go b/amqp/amqpclient.go index 9ab55f6..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 @@ -163,3 +163,16 @@ func PingAMQP() error { err := SendActionAMQP(a, "") return err } + +func CheckConnection() error { + + 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 new file mode 100644 index 0000000..c3b3ef5 --- /dev/null +++ b/routes/healthz/healthz_endpoint.go @@ -0,0 +1,47 @@ +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" + "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 {object} docs.ResponseError "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 { + 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 { + fmt.Println(err.Error()) + c.JSON(http.StatusInternalServerError, gin.H{ + "success:": false, + "message": err.Error(), + }) + return + } + } + + c.JSON(http.StatusOK, gin.H{}) +} diff --git a/routes/healthz/healthz_test.go b/routes/healthz/healthz_test.go new file mode 100644 index 0000000..f8c78d9 --- /dev/null +++ b/routes/healthz/healthz_test.go @@ -0,0 +1,66 @@ +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" + "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" + "github.com/stretchr/testify/assert" + "net/http" + + //"net/http" + "testing" +) + +var router *gin.Engine +var db *gorm.DB + +func TestHealthz(t *testing.T) { + // 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) + + // 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) + 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 }