diff --git a/dgraph/cmd/zero/http.go b/dgraph/cmd/zero/http.go index 8c07f8618d8..b36c7d18615 100644 --- a/dgraph/cmd/zero/http.go +++ b/dgraph/cmd/zero/http.go @@ -18,6 +18,7 @@ package zero import ( "context" + "encoding/json" "fmt" "net/http" "strconv" @@ -26,7 +27,9 @@ import ( "github.com/gogo/protobuf/jsonpb" "github.com/golang/glog" + "github.com/pkg/errors" + "github.com/dgraph-io/dgo/v230/protos/api" "github.com/dgraph-io/dgraph/protos/pb" "github.com/dgraph-io/dgraph/x" ) @@ -231,9 +234,56 @@ func (st *state) getState(w http.ResponseWriter, r *http.Request) { } } +func (s *Server) zeroHealth(ctx context.Context) (*api.Response, error) { + if ctx.Err() != nil { + return nil, errors.Wrap(ctx.Err(), "http request context error") + } + health := pb.HealthInfo{ + Instance: "zero", + Address: x.WorkerConfig.MyAddr, + Status: "healthy", + Version: x.Version(), + Uptime: int64(time.Since(x.WorkerConfig.StartTime) / time.Second), + LastEcho: time.Now().Unix(), + } + jsonOut, err := json.Marshal(health) + if err != nil { + return nil, errors.Wrapf(err, "unable to marshal zero health, error") + } + return &api.Response{Json: jsonOut}, nil +} + func (st *state) pingResponse(w http.ResponseWriter, r *http.Request) { x.AddCorsHeaders(w) - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("OK")) + /* + * zero is changed to also output the health in JSON format for client + * request header "Accept: application/json". + * + * Backward compatibility- Before this change the '/health' endpoint + * used to output the string OK. After the fix it returns OK when the + * client sends the request without "Accept: application/json" in its + * http header. + */ + switch r.Header.Get("Accept") { + case "application/json": + resp, err := (st.zero).zeroHealth(r.Context()) + if err != nil { + x.SetStatus(w, x.Error, err.Error()) + return + } + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + if _, err := w.Write(resp.Json); err != nil { + glog.Warningf("http error send failed, error msg=[%v]", err) + return + } + default: + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusOK) + if _, err := w.Write([]byte("OK")); err != nil { + glog.Warningf("http error send failed, error msg=[%v]", err) + return + } + } } diff --git a/dgraph/cmd/zero/zero_test.go b/dgraph/cmd/zero/zero_test.go index 148d4e02008..a708ed7a14f 100644 --- a/dgraph/cmd/zero/zero_test.go +++ b/dgraph/cmd/zero/zero_test.go @@ -20,8 +20,13 @@ package zero import ( "context" + "encoding/json" + "io/ioutil" "math" + "net/http" + "net/url" "testing" + "time" "github.com/stretchr/testify/require" "google.golang.org/grpc" @@ -112,3 +117,48 @@ func TestProposalKey(t *testing.T) { } require.Equal(t, len(uniqueKeys), 10, "each iteration should create unique key") } + +func TestZeroHealth(t *testing.T) { + client := http.Client{Timeout: 3 * time.Second} + u := &url.URL{ + Scheme: "http", + Host: testutil.ContainerAddr("zero1", 6080), + Path: "health", + } + + // JSON format + req, err := http.NewRequest("GET", u.String(), nil) + require.NoError(t, err) + + req.Header.Add("Accept", `application/json`) + start := time.Now().Unix() + resp, err := client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + var r map[string]interface{} + err = json.Unmarshal(body, &r) + require.NoError(t, err) + + require.Equal(t, "zero", r["instance"].(string)) + require.Equal(t, "zero1:5080", r["address"].(string)) + require.Equal(t, "healthy", r["status"].(string)) + require.NotEqual(t, 0, len(r["version"].(string))) + require.Greater(t, r["uptime"].(float64), 0.0) + require.GreaterOrEqual(t, int64(r["lastEcho"].(float64)), start) + + // String format + req, err = http.NewRequest("GET", u.String(), nil) + require.NoError(t, err) + + resp, err = client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + body, err = ioutil.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, string(body), "OK") +}