Add health api

This commit is contained in:
Jannis Mattheis 2019-08-11 14:09:10 +02:00
parent ad157a138b
commit 81c4a73df3
7 changed files with 197 additions and 0 deletions

46
api/health.go Normal file
View File

@ -0,0 +1,46 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/gotify/server/model"
)
// The HealthDatabase interface for encapsulating database access.
type HealthDatabase interface {
Ping() error
}
// The HealthAPI provides handlers for the health information.
type HealthAPI struct {
DB HealthDatabase
}
// Health returns health information.
// swagger:operation GET /health health getHealth
//
// Get health information.
//
// ---
// produces: [application/json]
// responses:
// 200:
// description: Ok
// schema:
// $ref: "#/definitions/Health"
// 500:
// description: Ok
// schema:
// $ref: "#/definitions/Health"
func (a *HealthAPI) Health(ctx *gin.Context) {
if err := a.DB.Ping(); err != nil {
ctx.JSON(500, model.Health{
Health: model.StatusOrange,
Database: model.StatusRed,
})
return
}
ctx.JSON(200, model.Health{
Health: model.StatusGreen,
Database: model.StatusGreen,
})
}

49
api/health_test.go Normal file
View File

@ -0,0 +1,49 @@
package api
import (
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/gotify/server/mode"
"github.com/gotify/server/model"
"github.com/gotify/server/test"
"github.com/gotify/server/test/testdb"
"github.com/stretchr/testify/suite"
)
func TestHealthSuite(t *testing.T) {
suite.Run(t, new(HealthSuite))
}
type HealthSuite struct {
suite.Suite
db *testdb.Database
a *HealthAPI
ctx *gin.Context
recorder *httptest.ResponseRecorder
}
func (s *HealthSuite) BeforeTest(suiteName, testName string) {
mode.Set(mode.TestDev)
s.recorder = httptest.NewRecorder()
s.db = testdb.NewDB(s.T())
s.ctx, _ = gin.CreateTestContext(s.recorder)
withURL(s.ctx, "http", "example.com")
s.a = &HealthAPI{DB: s.db}
}
func (s *HealthSuite) AfterTest(suiteName, testName string) {
s.db.Close()
}
func (s *HealthSuite) TestHealthSuccess() {
s.a.Health(s.ctx)
test.BodyEquals(s.T(), model.Health{Health: model.StatusGreen, Database: model.StatusGreen}, s.recorder)
}
func (s *HealthSuite) TestDatabaseFailure() {
s.db.Close()
s.a.Health(s.ctx)
test.BodyEquals(s.T(), model.Health{Health: model.StatusOrange, Database: model.StatusRed}, s.recorder)
}

6
database/ping.go Normal file
View File

@ -0,0 +1,6 @@
package database
// Ping pings the database to verify the connection.
func (d *GormDatabase) Ping() error {
return d.DB.DB().Ping()
}

16
database/ping_test.go Normal file
View File

@ -0,0 +1,16 @@
package database
import (
"github.com/stretchr/testify/assert"
)
func (s *DatabaseSuite) TestPing_onValidDB() {
err := s.db.Ping()
assert.NoError(s.T(), err)
}
func (s *DatabaseSuite) TestPing_onClosedDB() {
s.db.Close()
err := s.db.Ping()
assert.Error(s.T(), err)
}

View File

@ -839,6 +839,32 @@
} }
} }
}, },
"/health": {
"get": {
"produces": [
"application/json"
],
"tags": [
"health"
],
"summary": "Get health information.",
"operationId": "getHealth",
"responses": {
"200": {
"description": "Ok",
"schema": {
"$ref": "#/definitions/Health"
}
},
"500": {
"description": "Ok",
"schema": {
"$ref": "#/definitions/Health"
}
}
}
}
},
"/message": { "/message": {
"get": { "get": {
"security": [ "security": [
@ -1969,6 +1995,30 @@
}, },
"x-go-package": "github.com/gotify/server/model" "x-go-package": "github.com/gotify/server/model"
}, },
"Health": {
"description": "Health represents how healthy the application is.",
"type": "object",
"title": "Health Model",
"required": [
"health",
"database"
],
"properties": {
"database": {
"description": "The health of the database connection.",
"type": "string",
"x-go-name": "Database",
"example": "green"
},
"health": {
"description": "The health of the overall application.",
"type": "string",
"x-go-name": "Health",
"example": "green"
}
},
"x-go-package": "github.com/gotify/server/model"
},
"Message": { "Message": {
"description": "The MessageExternal holds information about a message which was sent by an Application.", "description": "The MessageExternal holds information about a message which was sent by an Application.",
"type": "object", "type": "object",

28
model/health.go Normal file
View File

@ -0,0 +1,28 @@
package model
// Health Model
//
// Health represents how healthy the application is.
//
// swagger:model Health
type Health struct {
// The health of the overall application.
//
// required: true
// example: green
Health string `json:"health"`
// The health of the database connection.
//
// required: true
// example: green
Database string `json:"database"`
}
const (
// StatusGreen everything is alright.
StatusGreen = "green"
// StatusOrange some things are alright.
StatusOrange = "orange"
// StatusRed nothing is alright.
StatusRed = "red"
)

View File

@ -29,6 +29,7 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co
streamHandler := stream.New(200*time.Second, 15*time.Second, conf.Server.Stream.AllowedOrigins) streamHandler := stream.New(200*time.Second, 15*time.Second, conf.Server.Stream.AllowedOrigins)
authentication := auth.Auth{DB: db} authentication := auth.Auth{DB: db}
messageHandler := api.MessageAPI{Notifier: streamHandler, DB: db} messageHandler := api.MessageAPI{Notifier: streamHandler, DB: db}
healthHandler := api.HealthAPI{DB: db}
clientHandler := api.ClientAPI{ clientHandler := api.ClientAPI{
DB: db, DB: db,
ImageDir: conf.UploadedImagesDir, ImageDir: conf.UploadedImagesDir,
@ -57,6 +58,7 @@ func Create(db *database.GormDatabase, vInfo *model.VersionInfo, conf *config.Co
ui.Register(g) ui.Register(g)
g.GET("/health", healthHandler.Health)
g.GET("/swagger", docs.Serve) g.GET("/swagger", docs.Serve)
g.Static("/image", conf.UploadedImagesDir) g.Static("/image", conf.UploadedImagesDir)
g.GET("/docs", docs.UI) g.GET("/docs", docs.UI)