mirror of
https://github.com/astaxie/beego.git
synced 2024-11-22 09:00:55 +00:00
Merge pull request #4055 from tayoogunbiyi/develop
Allow Healthcheck endpoint return JSON for Kubernetes
This commit is contained in:
commit
28d3f624a3
62
admin.go
62
admin.go
@ -21,6 +21,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -71,7 +72,7 @@ func init() {
|
|||||||
// AdminIndex is the default http.Handler for admin module.
|
// AdminIndex is the default http.Handler for admin module.
|
||||||
// it matches url pattern "/".
|
// it matches url pattern "/".
|
||||||
func adminIndex(rw http.ResponseWriter, _ *http.Request) {
|
func adminIndex(rw http.ResponseWriter, _ *http.Request) {
|
||||||
execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl)
|
writeTemplate(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter.
|
// QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter.
|
||||||
@ -91,7 +92,7 @@ func qpsIndex(rw http.ResponseWriter, _ *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
execTpl(rw, data, qpsTpl, defaultScriptsTpl)
|
writeTemplate(rw, data, qpsTpl, defaultScriptsTpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListConf is the http.Handler of displaying all beego configuration values as key/value pair.
|
// ListConf is the http.Handler of displaying all beego configuration values as key/value pair.
|
||||||
@ -128,7 +129,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
data["Content"] = content
|
data["Content"] = content
|
||||||
data["Title"] = "Routers"
|
data["Title"] = "Routers"
|
||||||
execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
|
writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl)
|
||||||
case "filter":
|
case "filter":
|
||||||
var (
|
var (
|
||||||
content = M{
|
content = M{
|
||||||
@ -171,7 +172,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
data["Content"] = content
|
data["Content"] = content
|
||||||
data["Title"] = "Filters"
|
data["Title"] = "Filters"
|
||||||
execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
|
writeTemplate(rw, data, routerAndFilterTpl, defaultScriptsTpl)
|
||||||
default:
|
default:
|
||||||
rw.Write([]byte("command not support"))
|
rw.Write([]byte("command not support"))
|
||||||
}
|
}
|
||||||
@ -279,9 +280,7 @@ func profIndex(rw http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
writeJSON(rw, dataJSON)
|
||||||
rw.Header().Set("Content-Type", "application/json")
|
|
||||||
rw.Write(dataJSON)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,12 +289,12 @@ func profIndex(rw http.ResponseWriter, r *http.Request) {
|
|||||||
if command == "gc summary" {
|
if command == "gc summary" {
|
||||||
defaultTpl = gcAjaxTpl
|
defaultTpl = gcAjaxTpl
|
||||||
}
|
}
|
||||||
execTpl(rw, data, profillingTpl, defaultTpl)
|
writeTemplate(rw, data, profillingTpl, defaultTpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Healthcheck is a http.Handler calling health checking and showing the result.
|
// Healthcheck is a http.Handler calling health checking and showing the result.
|
||||||
// it's in "/healthcheck" pattern in admin module.
|
// it's in "/healthcheck" pattern in admin module.
|
||||||
func healthcheck(rw http.ResponseWriter, _ *http.Request) {
|
func healthcheck(rw http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
result []string
|
result []string
|
||||||
data = make(map[interface{}]interface{})
|
data = make(map[interface{}]interface{})
|
||||||
@ -322,10 +321,49 @@ func healthcheck(rw http.ResponseWriter, _ *http.Request) {
|
|||||||
*resultList = append(*resultList, result)
|
*resultList = append(*resultList, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
queryParams := r.URL.Query()
|
||||||
|
jsonFlag := queryParams.Get("json")
|
||||||
|
shouldReturnJSON, _ := strconv.ParseBool(jsonFlag)
|
||||||
|
|
||||||
|
if shouldReturnJSON {
|
||||||
|
response := buildHealthCheckResponseList(resultList)
|
||||||
|
jsonResponse, err := json.Marshal(response)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
} else {
|
||||||
|
writeJSON(rw, jsonResponse)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
content["Data"] = resultList
|
content["Data"] = resultList
|
||||||
data["Content"] = content
|
data["Content"] = content
|
||||||
data["Title"] = "Health Check"
|
data["Title"] = "Health Check"
|
||||||
execTpl(rw, data, healthCheckTpl, defaultScriptsTpl)
|
|
||||||
|
writeTemplate(rw, data, healthCheckTpl, defaultScriptsTpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildHealthCheckResponseList(healthCheckResults *[][]string) []map[string]interface{} {
|
||||||
|
response := make([]map[string]interface{}, len(*healthCheckResults))
|
||||||
|
|
||||||
|
for i, healthCheckResult := range *healthCheckResults {
|
||||||
|
currentResultMap := make(map[string]interface{})
|
||||||
|
|
||||||
|
currentResultMap["name"] = healthCheckResult[0]
|
||||||
|
currentResultMap["message"] = healthCheckResult[1]
|
||||||
|
currentResultMap["status"] = healthCheckResult[2]
|
||||||
|
|
||||||
|
response[i] = currentResultMap
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeJSON(rw http.ResponseWriter, jsonData []byte) {
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
rw.Write(jsonData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaskStatus is a http.Handler with running task status (task name, status and the last execution).
|
// TaskStatus is a http.Handler with running task status (task name, status and the last execution).
|
||||||
@ -371,10 +409,10 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) {
|
|||||||
content["Data"] = resultList
|
content["Data"] = resultList
|
||||||
data["Content"] = content
|
data["Content"] = content
|
||||||
data["Title"] = "Tasks"
|
data["Title"] = "Tasks"
|
||||||
execTpl(rw, data, tasksTpl, defaultScriptsTpl)
|
writeTemplate(rw, data, tasksTpl, defaultScriptsTpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func execTpl(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) {
|
func writeTemplate(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) {
|
||||||
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
|
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
|
||||||
for _, tpl := range tpls {
|
for _, tpl := range tpls {
|
||||||
tmpl = template.Must(tmpl.Parse(tpl))
|
tmpl = template.Must(tmpl.Parse(tpl))
|
||||||
|
162
admin_test.go
162
admin_test.go
@ -1,10 +1,32 @@
|
|||||||
package beego
|
package beego
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/toolbox"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SampleDatabaseCheck struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type SampleCacheCheck struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *SampleDatabaseCheck) Check() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *SampleCacheCheck) Check() error {
|
||||||
|
return errors.New("no cache detected")
|
||||||
|
}
|
||||||
|
|
||||||
func TestList_01(t *testing.T) {
|
func TestList_01(t *testing.T) {
|
||||||
m := make(M)
|
m := make(M)
|
||||||
list("BConfig", BConfig, m)
|
list("BConfig", BConfig, m)
|
||||||
@ -75,3 +97,143 @@ func oldMap() M {
|
|||||||
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
|
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWriteJSON(t *testing.T) {
|
||||||
|
t.Log("Testing the adding of JSON to the response")
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
originalBody := []int{1, 2, 3}
|
||||||
|
|
||||||
|
res, _ := json.Marshal(originalBody)
|
||||||
|
|
||||||
|
writeJSON(w, res)
|
||||||
|
|
||||||
|
decodedBody := []int{}
|
||||||
|
err := json.NewDecoder(w.Body).Decode(&decodedBody)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Could not decode response body into slice.")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range decodedBody {
|
||||||
|
if decodedBody[i] != originalBody[i] {
|
||||||
|
t.Fatalf("Expected %d but got %d in decoded body slice", originalBody[i], decodedBody[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHealthCheckHandlerDefault(t *testing.T) {
|
||||||
|
endpointPath := "/healthcheck"
|
||||||
|
|
||||||
|
toolbox.AddHealthCheck("database", &SampleDatabaseCheck{})
|
||||||
|
toolbox.AddHealthCheck("cache", &SampleCacheCheck{})
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", endpointPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler := http.HandlerFunc(healthcheck)
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if status := w.Code; status != http.StatusOK {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||||
|
status, http.StatusOK)
|
||||||
|
}
|
||||||
|
if !strings.Contains(w.Body.String(), "database") {
|
||||||
|
t.Errorf("Expected 'database' in generated template.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildHealthCheckResponseList(t *testing.T) {
|
||||||
|
healthCheckResults := [][]string{
|
||||||
|
[]string{
|
||||||
|
"error",
|
||||||
|
"Database",
|
||||||
|
"Error occured whie starting the db",
|
||||||
|
},
|
||||||
|
[]string{
|
||||||
|
"success",
|
||||||
|
"Cache",
|
||||||
|
"Cache started successfully",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
responseList := buildHealthCheckResponseList(&healthCheckResults)
|
||||||
|
|
||||||
|
if len(responseList) != len(healthCheckResults) {
|
||||||
|
t.Errorf("invalid response map length: got %d want %d",
|
||||||
|
len(responseList), len(healthCheckResults))
|
||||||
|
}
|
||||||
|
|
||||||
|
responseFields := []string{"name", "message", "status"}
|
||||||
|
|
||||||
|
for _, response := range responseList {
|
||||||
|
for _, field := range responseFields {
|
||||||
|
_, ok := response[field]
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected %s to be in the response %v", field, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHealthCheckHandlerReturnsJSON(t *testing.T) {
|
||||||
|
|
||||||
|
toolbox.AddHealthCheck("database", &SampleDatabaseCheck{})
|
||||||
|
toolbox.AddHealthCheck("cache", &SampleCacheCheck{})
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/healthcheck?json=true", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler := http.HandlerFunc(healthcheck)
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
if status := w.Code; status != http.StatusOK {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||||
|
status, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedResponseBody := []map[string]interface{}{}
|
||||||
|
expectedResponseBody := []map[string]interface{}{}
|
||||||
|
|
||||||
|
expectedJSONString := []byte(`
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"message":"database",
|
||||||
|
"name":"success",
|
||||||
|
"status":"OK"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"message":"cache",
|
||||||
|
"name":"error",
|
||||||
|
"status":"no cache detected"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
json.Unmarshal(expectedJSONString, &expectedResponseBody)
|
||||||
|
|
||||||
|
json.Unmarshal(w.Body.Bytes(), &decodedResponseBody)
|
||||||
|
|
||||||
|
if len(expectedResponseBody) != len(decodedResponseBody) {
|
||||||
|
t.Errorf("invalid response map length: got %d want %d",
|
||||||
|
len(decodedResponseBody), len(expectedResponseBody))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(decodedResponseBody, expectedResponseBody) {
|
||||||
|
t.Errorf("handler returned unexpected body: got %v want %v",
|
||||||
|
decodedResponseBody, expectedResponseBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user