Skip to content

Commit

Permalink
Support Accept: application/json on common HTTP endpoints (#2673)
Browse files Browse the repository at this point in the history
* Added json support to rings, user stats, ha tracker

Signed-off-by: Joe Elliott <[email protected]>

* Added services template and accepts json support

Signed-off-by: Joe Elliott <[email protected]>

* lint + changelog

Signed-off-by: Joe Elliott <[email protected]>

* Improved json field names

Signed-off-by: Joe Elliott <[email protected]>

* Fixed accept header name and removed jpe notes

Signed-off-by: Joe Elliott <[email protected]>

* Added test

Signed-off-by: Joe Elliott <[email protected]>

* lint

Signed-off-by: Joe Elliott <[email protected]>

* Removed unused CSRF Token Placeholder

Signed-off-by: Joe Elliott <[email protected]>
  • Loading branch information
joe-elliott authored Jun 2, 2020
1 parent 58790db commit 80b1261
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 35 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@
* TSDB now does memory-mapping of Head chunks and reduces memory usage.
* [ENHANCEMENT] Experimental TSDB: when `-querier.query-store-after` is configured and running the experimental blocks storage, the time range of the query sent to the store is now manipulated to ensure the query end time is not more recent than 'now - query-store-after'. #2642
* [ENHANCEMENT] Experimental TSDB: small performance improvement in concurrent usage of RefCache, used during samples ingestion. #2651
* [ENHANCEMENT] The following endpoints now respond appropriately to an `Accepts` header with the value `application/json` #2673
* `/distributor/all_user_stats`
* `/distributor/ha_tracker`
* `/ingester/ring`
* `/store-gateway/ring`
* `/compactor/ring`
* `/ruler/ring`
* `/services`
* [ENHANCEMENT] Add `-cassandra.num-connections` to allow increasing the number of TCP connections to each Cassandra server. #2666
* [ENHANCEMENT] Use separate Cassandra clients and connections for reads and writes. #2666
* [BUGFIX] Ruler: Ensure temporary rule files with special characters are properly mapped and cleaned up. #2506
Expand Down
63 changes: 58 additions & 5 deletions pkg/cortex/status.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,71 @@
package cortex

import (
"fmt"
"html/template"
"net/http"
"time"

"github.com/cortexproject/cortex/pkg/util"
)

const tpl = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Cortex Services Status</title>
</head>
<body>
<h1>Cortex Services Status</h1>
<p>Current time: {{ .Now }}</p>
<table border="1">
<thead>
<tr>
<th>Service</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{{ range .Services }}
<tr>
<td>{{ .Name }}</td>
<td>{{ .Status }}</td>
</tr>
{{ end }}
</tbody>
</table>
</body>
</html>`

var tmpl *template.Template

type renderService struct {
Name string `json:"name"`
Status string `json:"status"`
}

func init() {
tmpl = template.Must(template.New("webpage").Parse(tpl))
}

func (t *Cortex) servicesHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Header().Set("Content-Type", "text/plain")

// TODO: this could be extended to also print sub-services, if given service has any
svcs := make([]renderService, 0)
for mod, s := range t.ServiceMap {
if s != nil {
fmt.Fprintf(w, "%v => %v\n", mod, s.State())
}
svcs = append(svcs, renderService{
Name: mod,
Status: s.State().String(),
})
}

// TODO: this could be extended to also print sub-services, if given service has any
util.RenderHTTPResponse(w, struct {
Now time.Time `json:"now"`
Services []renderService `json:"services"`
}{
Now: time.Now(),
Services: svcs,
}, tmpl, r)
}
22 changes: 12 additions & 10 deletions pkg/distributor/ha_tracker_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"time"

"github.com/prometheus/prometheus/pkg/timestamp"

"github.com/cortexproject/cortex/pkg/util"
)

const trackerTpl = `
Expand Down Expand Up @@ -56,9 +58,12 @@ func init() {
func (h *haTracker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
h.electedLock.RLock()
type replica struct {
UserID, Cluster, Replica string
ElectedAt time.Time
UpdateTime, FailoverTime time.Duration
UserID string `json:"userID"`
Cluster string `json:"cluster"`
Replica string `json:"replica"`
ElectedAt time.Time `json:"electedAt"`
UpdateTime time.Duration `json:"updateDuration"`
FailoverTime time.Duration `json:"failoverDuration"`
}

electedReplicas := []replica{}
Expand Down Expand Up @@ -86,14 +91,11 @@ func (h *haTracker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return first.Cluster < second.Cluster
})

if err := trackerTmpl.Execute(w, struct {
Elected []replica
Now time.Time
util.RenderHTTPResponse(w, struct {
Elected []replica `json:"elected"`
Now time.Time `json:"now"`
}{
Elected: electedReplicas,
Now: time.Now(),
}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}, trackerTmpl, req)
}
15 changes: 7 additions & 8 deletions pkg/distributor/http_admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"sort"
"strings"
"time"

"github.com/cortexproject/cortex/pkg/util"
)

const tpl = `
Expand Down Expand Up @@ -83,16 +85,13 @@ func (d *Distributor) AllUserStatsHandler(w http.ResponseWriter, r *http.Request
return
}

if err := tmpl.Execute(w, struct {
Now time.Time
Stats []UserIDStats
ReplicationFactor int
util.RenderHTTPResponse(w, struct {
Now time.Time `json:"now"`
Stats []UserIDStats `json:"stats"`
ReplicationFactor int `json:"replicationFactor"`
}{
Now: time.Now(),
Stats: stats,
ReplicationFactor: d.ingestersRing.ReplicationFactor(),
}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}, tmpl, r)
}
25 changes: 13 additions & 12 deletions pkg/ring/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,14 @@ func (r *Ring) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}

ingesters = append(ingesters, struct {
ID, State, Address, Timestamp, Zone string
Tokens []uint32
NumTokens int
Ownership float64
ID string `json:"id"`
State string `json:"state"`
Address string `json:"address"`
Timestamp string `json:"timestamp"`
Zone string `json:"zone"`
Tokens []uint32 `json:"tokens"`
NumTokens int `json:"-"`
Ownership float64 `json:"-"`
}{
ID: id,
State: state,
Expand All @@ -158,16 +162,13 @@ func (r *Ring) ServeHTTP(w http.ResponseWriter, req *http.Request) {

tokensParam := req.URL.Query().Get("tokens")

if err := pageTemplate.Execute(w, struct {
Ingesters []interface{}
Now time.Time
ShowTokens bool
util.RenderHTTPResponse(w, struct {
Ingesters []interface{} `json:"shards"`
Now time.Time `json:"now"`
ShowTokens bool `json:"-"`
}{
Ingesters: ingesters,
Now: time.Now(),
ShowTokens: tokensParam == "true",
}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}, pageTemplate, req)
}
17 changes: 17 additions & 0 deletions pkg/util/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"context"
"encoding/json"
"fmt"
"html/template"
"io"
"net/http"
"strings"

"github.com/blang/semver"
"github.com/gogo/protobuf/proto"
Expand All @@ -29,6 +31,21 @@ func WriteJSONResponse(w http.ResponseWriter, v interface{}) {
w.Header().Set("Content-Type", "application/json")
}

// RenderHTTPResponse either responds with json or a rendered html page using the passed in template
// by checking the Accepts header
func RenderHTTPResponse(w http.ResponseWriter, v interface{}, t *template.Template, r *http.Request) {
accept := r.Header.Get("Accept")
if strings.Contains(accept, "application/json") {
WriteJSONResponse(w, v)
return
}

err := t.Execute(w, v)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

// CompressionType for encoding and decoding requests and responses.
type CompressionType int

Expand Down
63 changes: 63 additions & 0 deletions pkg/util/http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package util

import (
"html/template"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

func TestRenderHTTPResponse(t *testing.T) {
type testStruct struct {
Name string `json:"name"`
Value int `json:"value"`
}

tests := []struct {
name string
headers map[string]string
tmpl string
expected string
value testStruct
}{
{
name: "Test Renders json",
headers: map[string]string{
"Accept": "application/json",
},
tmpl: "<html></html>",
expected: `{"name":"testName","value":42}`,
value: testStruct{
Name: "testName",
Value: 42,
},
},
{
name: "Test Renders html",
headers: map[string]string{},
tmpl: "<html>{{ .Name }}</html>",
expected: "<html>testName</html>",
value: testStruct{
Name: "testName",
Value: 42,
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpl := template.Must(template.New("webpage").Parse(tt.tmpl))
writer := httptest.NewRecorder()
request := httptest.NewRequest("GET", "/", nil)

for k, v := range tt.headers {
request.Header.Add(k, v)
}

RenderHTTPResponse(writer, tt.value, tmpl, request)

assert.Equal(t, tt.expected, writer.Body.String())
})
}
}

0 comments on commit 80b1261

Please sign in to comment.