Merge pull request #1326 from influxdb/http-json-endpoint

Http json write series endpoint
pull/1333/head
Cory LaNou 2015-01-18 14:43:20 -07:00
commit 3e54774600
3 changed files with 127 additions and 45 deletions

View File

@ -8,6 +8,7 @@ import (
"net/url"
"strconv"
"strings"
"time"
"github.com/bmizerany/pat"
"github.com/influxdb/influxdb/influxql"
@ -69,7 +70,7 @@ func NewHandler(s *Server) *Handler {
h.mux.Get("/query", h.makeAuthenticationHandler(h.serveQuery))
// Data-ingest route.
h.mux.Post("/db/:db/series", h.makeAuthenticationHandler(h.serveWriteSeries))
h.mux.Post("/write", h.makeAuthenticationHandler(h.serveWrite))
// Data node routes.
h.mux.Get("/data_nodes", h.makeAuthenticationHandler(h.serveDataNodes))
@ -156,59 +157,71 @@ func (h *Handler) serveQuery(w http.ResponseWriter, r *http.Request, u *User) {
_ = json.NewEncoder(w).Encode(results)
}
// serveWriteSeries receives incoming series data and writes it to the database.
func (h *Handler) serveWriteSeries(w http.ResponseWriter, r *http.Request, u *User) {
// TODO: Authentication.
type batchWrite struct {
Points []Point `json:"points"`
Database string `json:"database"`
RetentionPolicy string `json:"retentionPolicy"`
Tags map[string]string `json:"tags"`
Timestamp time.Time `json:"timestamp"`
}
/* TEMPORARILY REMOVED FOR PROTOBUFS.
// Retrieve database from server.
db := h.server.Database(r.URL.Query().Get(":db"))
if db == nil {
h.error(w, ErrDatabaseNotFound.Error(), http.StatusNotFound)
return
}
// serveWrite receives incoming series data and writes it to the database.
func (h *Handler) serveWrite(w http.ResponseWriter, r *http.Request, u *User) {
var br batchWrite
// Parse time precision from query parameters.
precision, err := parseTimePrecision(r.URL.Query().Get("time_precision"))
if err != nil {
h.error(w, err.Error(), http.StatusBadRequest)
return
}
// Setup HTTP request reader. Wrap in a gzip reader if encoding set in header.
reader := r.Body
if r.Header.Get("Content-Encoding") == "gzip" {
if reader, err = gzip.NewReader(r.Body); err != nil {
h.error(w, err.Error(), http.StatusBadRequest)
return
}
}
// Decode series from reader.
ss := []*serializedSeries{}
dec := json.NewDecoder(reader)
dec := json.NewDecoder(r.Body)
dec.UseNumber()
if err := dec.Decode(&ss); err != nil {
h.error(w, err.Error(), http.StatusBadRequest)
var writeError = func(result Result, statusCode int) {
w.WriteHeader(statusCode)
w.Header().Add("content-type", "application/json")
_ = json.NewEncoder(w).Encode(&result)
return
}
// Convert the wire format to the internal representation of the time series.
series, err := serializedSeriesSlice(ss).series(precision)
if err != nil {
h.error(w, err.Error(), http.StatusBadRequest)
return
}
// Write series data to the database.
// TODO: Allow multiple series written to DB at once.
for _, s := range series {
if err := db.WriteSeries(s); err != nil {
h.error(w, err.Error(), http.StatusInternalServerError)
for {
if err := dec.Decode(&br); err != nil {
if err.Error() == "EOF" {
w.WriteHeader(http.StatusOK)
return
}
writeError(Result{Err: err}, http.StatusInternalServerError)
return
}
if br.Database == "" {
writeError(Result{Err: fmt.Errorf("database is required")}, http.StatusInternalServerError)
return
}
if h.server.databases[br.Database] == nil {
writeError(Result{Err: fmt.Errorf("database not found: %q", br.Database)}, http.StatusNotFound)
return
}
// TODO corylanou: Check if user can write to specified database
//if !user_can_write(br.Database) {
//writeError(&Result{Err: fmt.Errorf("%q user is not authorized to write to database %q", u.Name)}, http.StatusUnauthorized)
//return
//}
for _, p := range br.Points {
if p.Timestamp.IsZero() {
p.Timestamp = br.Timestamp
}
if len(br.Tags) > 0 {
for k, _ := range br.Tags {
if p.Tags[k] == "" {
p.Tags[k] = br.Tags[k]
}
}
}
if _, err := h.server.WriteSeries(br.Database, br.RetentionPolicy, []Point{p}); err != nil {
writeError(Result{Err: err}, http.StatusInternalServerError)
return
}
}
}
*/
}
// serveMetastore returns a copy of the metastore.

View File

@ -672,6 +672,72 @@ func TestHandler_AuthenticatedDatabases_UnauthorizedBasicAuth(t *testing.T) {
}
}
func TestHandler_serveWriteSeries(t *testing.T) {
srvr := OpenServer(NewMessagingClient())
srvr.CreateDatabase("foo")
srvr.CreateRetentionPolicy("foo", influxdb.NewRetentionPolicy("bar"))
s := NewHTTPServer(srvr)
defer s.Close()
status, _ := MustHTTP("POST", s.URL+`/write`, nil, nil, `{"database" : "foo", "retentionPolicy" : "bar", "points": [{"name": "cpu", "tags": {"host": "server01"},"timestamp": "2009-11-10T23:00:00Z","values": {"value": 100}}]}`)
if status != http.StatusOK {
t.Fatalf("unexpected status: %d", status)
}
}
func TestHandler_serveWriteSeries_noDatabaseExists(t *testing.T) {
srvr := OpenServer(NewMessagingClient())
s := NewHTTPServer(srvr)
defer s.Close()
status, body := MustHTTP("POST", s.URL+`/write`, nil, nil, `{"database" : "foo", "retentionPolicy" : "bar", "points": [{"name": "cpu", "tags": {"host": "server01"},"timestamp": "2009-11-10T23:00:00Z","values": {"value": 100}}]}`)
expectedStatus := http.StatusNotFound
if status != expectedStatus {
t.Fatalf("unexpected status: expected: %d, actual: %d", expectedStatus, status)
}
response := `{"error":"database not found: \"foo\""}`
if body != response {
t.Fatalf("unexpected body: expected %s, actual %s", response, body)
}
}
func TestHandler_serveWriteSeries_invalidJSON(t *testing.T) {
srvr := OpenServer(NewMessagingClient())
s := NewHTTPServer(srvr)
defer s.Close()
status, body := MustHTTP("POST", s.URL+`/write`, nil, nil, `{"database" : foo", "retentionPolicy" : "bar", "points": [{"name": "cpu", "tags": {"host": "server01"},"timestamp": "2009-11-10T23:00:00Z","values": {"value": 100}}]}`)
if status != http.StatusInternalServerError {
t.Fatalf("unexpected status: expected: %d, actual: %d", http.StatusInternalServerError, status)
}
response := `{"error":"invalid character 'o' in literal false (expecting 'a')"}`
if body != response {
t.Fatalf("unexpected body: expected %s, actual %s", response, body)
}
}
func TestHandler_serveWriteSeries_noDatabaseSpecified(t *testing.T) {
srvr := OpenServer(NewMessagingClient())
s := NewHTTPServer(srvr)
defer s.Close()
status, body := MustHTTP("POST", s.URL+`/write`, nil, nil, `{}`)
if status != http.StatusInternalServerError {
t.Fatalf("unexpected status: expected: %d, actual: %d", http.StatusInternalServerError, status)
}
response := `{"error":"database is required"}`
if body != response {
t.Fatalf("unexpected body: expected %s, actual %s", response, body)
}
}
// Utility functions for this test suite.
func MustHTTP(verb, path string, params, headers map[string]string, body string) (int, string) {

View File

@ -1402,6 +1402,9 @@ func (s *Server) createSeriesIfNotExists(database, name string, tags map[string]
// Try to find series locally first.
s.mu.RLock()
idx := s.databases[database]
if idx == nil {
return 0, fmt.Errorf("database not found %q", database)
}
if _, series := idx.MeasurementAndSeries(name, tags); series != nil {
s.mu.RUnlock()
return series.ID, nil