1346 lines
39 KiB
Go
1346 lines
39 KiB
Go
package influxdb
|
|
|
|
/*
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"compress/zlib"
|
|
"crypto/tls"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
libhttp "net/http"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
log "code.google.com/p/log4go"
|
|
"github.com/bmizerany/pat"
|
|
"github.com/influxdb/influxdb/api"
|
|
"github.com/influxdb/influxdb/cluster"
|
|
. "github.com/influxdb/influxdb/common"
|
|
"github.com/influxdb/influxdb/configuration"
|
|
"github.com/influxdb/influxdb/coordinator"
|
|
"github.com/influxdb/influxdb/parser"
|
|
"github.com/influxdb/influxdb/protocol"
|
|
)
|
|
|
|
type HTTPServer struct {
|
|
conn net.Listener
|
|
sslConn net.Listener
|
|
httpPort string
|
|
httpSslPort string
|
|
httpSslCert string
|
|
adminAssetsDir string
|
|
coordinator api.Coordinator
|
|
userManager UserManager
|
|
shutdown chan bool
|
|
clusterConfig *cluster.ClusterConfiguration
|
|
raftServer *coordinator.RaftServer
|
|
readTimeout time.Duration
|
|
config *configuration.Configuration
|
|
p *pat.PatternServeMux
|
|
}
|
|
|
|
func NewHTTPServer(config *configuration.Configuration, theCoordinator api.Coordinator, userManager UserManager, clusterConfig *cluster.ClusterConfiguration, raftServer *coordinator.RaftServer) *HTTPServer {
|
|
self := &HTTPServer{}
|
|
self.httpPort = config.ApiHttpPortString()
|
|
self.adminAssetsDir = config.AdminAssetsDir
|
|
self.coordinator = theCoordinator
|
|
self.userManager = userManager
|
|
self.shutdown = make(chan bool, 2)
|
|
self.clusterConfig = clusterConfig
|
|
self.raftServer = raftServer
|
|
self.readTimeout = config.ApiReadTimeout
|
|
self.config = config
|
|
self.p = pat.New()
|
|
return self
|
|
}
|
|
|
|
const (
|
|
INVALID_CREDENTIALS_MSG = "Invalid database/username/password"
|
|
JSON_PRETTY_PRINT_INDENT = " "
|
|
)
|
|
|
|
func isPretty(r *libhttp.Request) bool {
|
|
return r.URL.Query().Get("pretty") == "true"
|
|
}
|
|
|
|
func (self *HTTPServer) EnableSsl(addr, certPath string) {
|
|
if addr == "" || certPath == "" {
|
|
// don't enable ssl unless both the address and the certificate
|
|
// path aren't empty
|
|
log.Info("Ssl will be disabled since the ssl port or certificate path weren't set")
|
|
return
|
|
}
|
|
|
|
self.httpSslPort = addr
|
|
self.httpSslCert = certPath
|
|
return
|
|
}
|
|
|
|
func (self *HTTPServer) ListenAndServe() {
|
|
var err error
|
|
if self.httpPort != "" {
|
|
self.conn, err = net.Listen("tcp", self.httpPort)
|
|
if err != nil {
|
|
log.Error("Listen: ", err)
|
|
}
|
|
}
|
|
self.Serve(self.conn)
|
|
}
|
|
|
|
func (self *HTTPServer) registerEndpoint(method string, pattern string, f libhttp.HandlerFunc) {
|
|
version := self.clusterConfig.GetLocalConfiguration().Version
|
|
switch method {
|
|
case "get":
|
|
self.p.Get(pattern, CompressionHeaderHandler(f, version))
|
|
case "post":
|
|
self.p.Post(pattern, HeaderHandler(f, version))
|
|
case "del":
|
|
self.p.Del(pattern, HeaderHandler(f, version))
|
|
}
|
|
self.p.Options(pattern, HeaderHandler(self.sendCrossOriginHeader, version))
|
|
}
|
|
|
|
func (self *HTTPServer) Serve(listener net.Listener) {
|
|
defer func() { self.shutdown <- true }()
|
|
|
|
self.conn = listener
|
|
|
|
// Run the given query and return an array of series or a chunked response
|
|
// with each batch of points we get back
|
|
self.registerEndpoint("get", "/db/:db/series", self.query)
|
|
|
|
// Write points to the given database
|
|
self.registerEndpoint("post", "/db/:db/series", self.writePoints)
|
|
self.registerEndpoint("del", "/db/:db/series/:series", self.dropSeries)
|
|
self.registerEndpoint("get", "/db", self.listDatabases)
|
|
self.registerEndpoint("post", "/db", self.createDatabase)
|
|
self.registerEndpoint("del", "/db/:name", self.dropDatabase)
|
|
|
|
// cluster admins management interface
|
|
self.registerEndpoint("get", "/cluster_admins", self.listClusterAdmins)
|
|
self.registerEndpoint("get", "/cluster_admins/authenticate", self.authenticateClusterAdmin)
|
|
self.registerEndpoint("post", "/cluster_admins", self.createClusterAdmin)
|
|
self.registerEndpoint("post", "/cluster_admins/:user", self.updateClusterAdmin)
|
|
self.registerEndpoint("del", "/cluster_admins/:user", self.deleteClusterAdmin)
|
|
|
|
// register profiling endpoints
|
|
registerProfilingEndpoints(self)
|
|
|
|
// db users management interface
|
|
self.registerEndpoint("get", "/db/:db/authenticate", self.authenticateDbUser)
|
|
self.registerEndpoint("get", "/db/:db/users", self.listDbUsers)
|
|
self.registerEndpoint("post", "/db/:db/users", self.createDbUser)
|
|
self.registerEndpoint("get", "/db/:db/users/:user", self.showDbUser)
|
|
self.registerEndpoint("del", "/db/:db/users/:user", self.deleteDbUser)
|
|
self.registerEndpoint("post", "/db/:db/users/:user", self.updateDbUser)
|
|
|
|
// healthcheck
|
|
self.registerEndpoint("get", "/ping", self.ping)
|
|
|
|
// force a raft log compaction
|
|
self.registerEndpoint("post", "/raft/force_compaction", self.forceRaftCompaction)
|
|
|
|
// fetch current list of available interfaces
|
|
self.registerEndpoint("get", "/interfaces", self.listInterfaces)
|
|
|
|
// cluster config endpoints
|
|
self.registerEndpoint("get", "/cluster/configuration", self.getClusterConfiguration)
|
|
self.registerEndpoint("get", "/cluster/servers", self.listServers)
|
|
self.registerEndpoint("del", "/cluster/servers/:id", self.removeServers)
|
|
self.registerEndpoint("post", "/cluster/shards", self.createShard)
|
|
self.registerEndpoint("get", "/cluster/shards", self.getShards)
|
|
self.registerEndpoint("del", "/cluster/shards/:id", self.dropShard)
|
|
self.registerEndpoint("get", "/cluster/shard_spaces", self.getShardSpaces)
|
|
self.registerEndpoint("post", "/cluster/shard_spaces/:db", self.createShardSpace)
|
|
self.registerEndpoint("del", "/cluster/shard_spaces/:db/:name", self.dropShardSpace)
|
|
self.registerEndpoint("post", "/cluster/shard_spaces/:db/:name", self.updateShardSpace)
|
|
self.registerEndpoint("post", "/cluster/database_configs/:db", self.configureDatabase)
|
|
|
|
// return whether the cluster is in sync or not
|
|
self.registerEndpoint("get", "/sync", self.isInSync)
|
|
|
|
if listener == nil {
|
|
self.startSsl(self.p)
|
|
return
|
|
}
|
|
|
|
go self.startSsl(self.p)
|
|
self.serveListener(listener, self.p)
|
|
}
|
|
|
|
func (self *HTTPServer) startSsl(p *pat.PatternServeMux) {
|
|
defer func() { self.shutdown <- true }()
|
|
|
|
// return if the ssl port or cert weren't set
|
|
if self.httpSslPort == "" || self.httpSslCert == "" {
|
|
return
|
|
}
|
|
|
|
log.Info("Starting SSL api on port %s using certificate in %s", self.httpSslPort, self.httpSslCert)
|
|
|
|
cert, err := tls.LoadX509KeyPair(self.httpSslCert, self.httpSslCert)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
self.sslConn, err = tls.Listen("tcp", self.httpSslPort, &tls.Config{
|
|
Certificates: []tls.Certificate{cert},
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
self.serveListener(self.sslConn, p)
|
|
}
|
|
|
|
func (self *HTTPServer) serveListener(listener net.Listener, p *pat.PatternServeMux) {
|
|
srv := &libhttp.Server{Handler: p, ReadTimeout: self.readTimeout}
|
|
if err := srv.Serve(listener); err != nil && !strings.Contains(err.Error(), "closed network") {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (self *HTTPServer) Close() {
|
|
if self.conn != nil {
|
|
log.Info("Closing http server")
|
|
self.conn.Close()
|
|
log.Info("Waiting for all requests to finish before killing the process")
|
|
select {
|
|
case <-time.After(time.Second * 5):
|
|
log.Error("There seems to be a hanging request. Closing anyway")
|
|
case <-self.shutdown:
|
|
}
|
|
}
|
|
}
|
|
|
|
type Writer interface {
|
|
yield(*protocol.Series) error
|
|
done()
|
|
}
|
|
|
|
type AllPointsWriter struct {
|
|
memSeries map[string]*protocol.Series
|
|
w libhttp.ResponseWriter
|
|
precision TimePrecision
|
|
pretty bool
|
|
}
|
|
|
|
func (self *AllPointsWriter) yield(series *protocol.Series) error {
|
|
oldSeries := self.memSeries[*series.Name]
|
|
if oldSeries == nil {
|
|
self.memSeries[*series.Name] = series
|
|
return nil
|
|
}
|
|
|
|
self.memSeries[series.GetName()] = MergeSeries(self.memSeries[series.GetName()], series)
|
|
return nil
|
|
}
|
|
|
|
func (self *AllPointsWriter) done() {
|
|
data, err := serializeMultipleSeries(self.memSeries, self.precision, self.pretty)
|
|
if err != nil {
|
|
self.w.WriteHeader(libhttp.StatusInternalServerError)
|
|
self.w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
self.w.Header().Add("content-type", "application/json")
|
|
self.w.WriteHeader(libhttp.StatusOK)
|
|
self.w.Write(data)
|
|
}
|
|
|
|
type ChunkWriter struct {
|
|
w libhttp.ResponseWriter
|
|
precision TimePrecision
|
|
wroteContentType bool
|
|
pretty bool
|
|
}
|
|
|
|
func (self *ChunkWriter) yield(series *protocol.Series) error {
|
|
data, err := serializeSingleSeries(series, self.precision, self.pretty)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !self.wroteContentType {
|
|
self.wroteContentType = true
|
|
self.w.Header().Add("content-type", "application/json")
|
|
}
|
|
self.w.WriteHeader(libhttp.StatusOK)
|
|
self.w.Write(data)
|
|
self.w.(libhttp.Flusher).Flush()
|
|
return nil
|
|
}
|
|
|
|
func (self *ChunkWriter) done() {
|
|
}
|
|
|
|
func TimePrecisionFromString(s string) (TimePrecision, error) {
|
|
switch s {
|
|
case "u":
|
|
return MicrosecondPrecision, nil
|
|
case "m":
|
|
log.Warn("time_precision=m will be disabled in future release, use time_precision=ms instead")
|
|
fallthrough
|
|
case "ms":
|
|
return MillisecondPrecision, nil
|
|
case "s":
|
|
return SecondPrecision, nil
|
|
case "":
|
|
return MillisecondPrecision, nil
|
|
}
|
|
|
|
return 0, fmt.Errorf("Unknown time precision %s", s)
|
|
}
|
|
|
|
func (self *HTTPServer) forceRaftCompaction(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(user User) (int, interface{}) {
|
|
self.coordinator.ForceCompaction(user)
|
|
return libhttp.StatusOK, "OK"
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) sendCrossOriginHeader(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
w.WriteHeader(libhttp.StatusOK)
|
|
}
|
|
|
|
func (self *HTTPServer) query(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
query := r.URL.Query().Get("q")
|
|
db := r.URL.Query().Get(":db")
|
|
pretty := isPretty(r)
|
|
|
|
self.tryAsDbUserAndClusterAdmin(w, r, func(user User) (int, interface{}) {
|
|
|
|
precision, err := TimePrecisionFromString(r.URL.Query().Get("time_precision"))
|
|
if err != nil {
|
|
return libhttp.StatusBadRequest, err.Error()
|
|
}
|
|
|
|
var writer Writer
|
|
if r.URL.Query().Get("chunked") == "true" {
|
|
writer = &ChunkWriter{w, precision, false, pretty}
|
|
} else {
|
|
writer = &AllPointsWriter{map[string]*protocol.Series{}, w, precision, pretty}
|
|
}
|
|
seriesWriter := NewSeriesWriter(writer.yield)
|
|
err = self.coordinator.RunQuery(user, db, query, seriesWriter)
|
|
if err != nil {
|
|
if e, ok := err.(*parser.QueryError); ok {
|
|
return errorToStatusCode(err), e.PrettyPrint()
|
|
}
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
|
|
writer.done()
|
|
return -1, nil
|
|
})
|
|
}
|
|
|
|
func errorToStatusCode(err error) int {
|
|
switch err.(type) {
|
|
case AuthenticationError:
|
|
return libhttp.StatusUnauthorized // HTTP 401
|
|
case AuthorizationError:
|
|
return libhttp.StatusForbidden // HTTP 403
|
|
case DatabaseExistsError:
|
|
return libhttp.StatusConflict // HTTP 409
|
|
default:
|
|
return libhttp.StatusBadRequest // HTTP 400
|
|
}
|
|
}
|
|
|
|
func (self *HTTPServer) writePoints(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
db := r.URL.Query().Get(":db")
|
|
precision, err := TimePrecisionFromString(r.URL.Query().Get("time_precision"))
|
|
if err != nil {
|
|
w.WriteHeader(libhttp.StatusBadRequest)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
|
|
self.tryAsDbUserAndClusterAdmin(w, r, func(user User) (int, interface{}) {
|
|
reader := r.Body
|
|
encoding := r.Header.Get("Content-Encoding")
|
|
switch encoding {
|
|
case "gzip":
|
|
reader, err = gzip.NewReader(r.Body)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
default:
|
|
// assume it's plain text
|
|
}
|
|
|
|
series, err := ioutil.ReadAll(reader)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
decoder := json.NewDecoder(bytes.NewBuffer(series))
|
|
decoder.UseNumber()
|
|
serializedSeries := []*SerializedSeries{}
|
|
err = decoder.Decode(&serializedSeries)
|
|
if err != nil {
|
|
return libhttp.StatusBadRequest, err.Error()
|
|
}
|
|
|
|
// convert the wire format to the internal representation of the time series
|
|
dataStoreSeries := make([]*protocol.Series, 0, len(serializedSeries))
|
|
for _, s := range serializedSeries {
|
|
if len(s.Points) == 0 {
|
|
continue
|
|
}
|
|
|
|
series, err := ConvertToDataStoreSeries(s, precision)
|
|
if err != nil {
|
|
return libhttp.StatusBadRequest, err.Error()
|
|
}
|
|
|
|
dataStoreSeries = append(dataStoreSeries, series)
|
|
}
|
|
|
|
err = self.coordinator.WriteSeriesData(user, db, dataStoreSeries)
|
|
|
|
if err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
|
|
return libhttp.StatusOK, nil
|
|
})
|
|
}
|
|
|
|
type createDatabaseRequest struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func (self *HTTPServer) listDatabases(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
databases, err := self.coordinator.ListDatabases(u)
|
|
if err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
return libhttp.StatusOK, databases
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) createDatabase(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(user User) (int, interface{}) {
|
|
createRequest := &createDatabaseRequest{}
|
|
decoder := json.NewDecoder(r.Body)
|
|
err := decoder.Decode(createRequest)
|
|
if err != nil {
|
|
return libhttp.StatusBadRequest, err.Error()
|
|
}
|
|
if !isValidDbName(createRequest.Name) {
|
|
m := "Unable to create database without name"
|
|
log.Error(m)
|
|
return libhttp.StatusBadRequest, m
|
|
}
|
|
err = self.coordinator.CreateDatabase(user, createRequest.Name)
|
|
if err != nil {
|
|
log.Error("Cannot create database %s. Error: %s", createRequest.Name, err)
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
log.Debug("Created database %s", createRequest.Name)
|
|
return libhttp.StatusCreated, nil
|
|
})
|
|
}
|
|
|
|
func isValidDbName(name string) bool {
|
|
return strings.TrimSpace(name) != ""
|
|
}
|
|
|
|
func (self *HTTPServer) dropDatabase(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(user User) (int, interface{}) {
|
|
name := r.URL.Query().Get(":name")
|
|
err := self.coordinator.DropDatabase(user, name)
|
|
if err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
return libhttp.StatusNoContent, nil
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) dropSeries(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
db := r.URL.Query().Get(":db")
|
|
series := r.URL.Query().Get(":series")
|
|
|
|
self.tryAsDbUserAndClusterAdmin(w, r, func(user User) (int, interface{}) {
|
|
f := func(s *protocol.Series) error {
|
|
return nil
|
|
}
|
|
seriesWriter := NewSeriesWriter(f)
|
|
err := self.coordinator.RunQuery(user, db, fmt.Sprintf("drop series %s", series), seriesWriter)
|
|
if err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
return libhttp.StatusNoContent, nil
|
|
})
|
|
}
|
|
|
|
type Point struct {
|
|
Timestamp int64 `json:"timestamp"`
|
|
SequenceNumber uint32 `json:"sequenceNumber"`
|
|
Values []interface{} `json:"values"`
|
|
}
|
|
|
|
func serializeSingleSeries(series *protocol.Series, precision TimePrecision, pretty bool) ([]byte, error) {
|
|
arg := map[string]*protocol.Series{"": series}
|
|
if pretty {
|
|
return json.MarshalIndent(SerializeSeries(arg, precision)[0], "", JSON_PRETTY_PRINT_INDENT)
|
|
} else {
|
|
return json.Marshal(SerializeSeries(arg, precision)[0])
|
|
}
|
|
}
|
|
|
|
func serializeMultipleSeries(series map[string]*protocol.Series, precision TimePrecision, pretty bool) ([]byte, error) {
|
|
if pretty {
|
|
return json.MarshalIndent(SerializeSeries(series, precision), "", JSON_PRETTY_PRINT_INDENT)
|
|
} else {
|
|
return json.Marshal(SerializeSeries(series, precision))
|
|
}
|
|
}
|
|
|
|
// // cluster admins management interface
|
|
|
|
func toBytes(body interface{}, pretty bool) ([]byte, string, error) {
|
|
if body == nil {
|
|
return nil, "text/plain", nil
|
|
}
|
|
switch x := body.(type) {
|
|
case string:
|
|
return []byte(x), "text/plain", nil
|
|
case []byte:
|
|
return x, "text/plain", nil
|
|
default:
|
|
// only JSON output is prettied up.
|
|
var b []byte
|
|
var e error
|
|
if pretty {
|
|
b, e = json.MarshalIndent(body, "", JSON_PRETTY_PRINT_INDENT)
|
|
} else {
|
|
b, e = json.Marshal(body)
|
|
}
|
|
return b, "application/json", e
|
|
}
|
|
}
|
|
|
|
func yieldUser(user User, yield func(User) (int, interface{}), pretty bool) (int, string, []byte) {
|
|
statusCode, body := yield(user)
|
|
bodyContent, contentType, err := toBytes(body, pretty)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, "text/plain", []byte(err.Error())
|
|
}
|
|
|
|
return statusCode, contentType, bodyContent
|
|
}
|
|
|
|
func getUsernameAndPassword(r *libhttp.Request) (string, string, error) {
|
|
q := r.URL.Query()
|
|
username, password := q.Get("u"), q.Get("p")
|
|
|
|
if username != "" && password != "" {
|
|
return username, password, nil
|
|
}
|
|
|
|
auth := r.Header.Get("Authorization")
|
|
if auth == "" {
|
|
return "", "", nil
|
|
}
|
|
|
|
fields := strings.Split(auth, " ")
|
|
if len(fields) != 2 {
|
|
return "", "", fmt.Errorf("Bad auth header")
|
|
}
|
|
|
|
bs, err := base64.StdEncoding.DecodeString(fields[1])
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("Bad encoding")
|
|
}
|
|
|
|
fields = strings.Split(string(bs), ":")
|
|
if len(fields) != 2 {
|
|
return "", "", fmt.Errorf("Bad auth value")
|
|
}
|
|
|
|
return fields[0], fields[1], nil
|
|
}
|
|
|
|
func (self *HTTPServer) tryAsClusterAdmin(w libhttp.ResponseWriter, r *libhttp.Request, yield func(User) (int, interface{})) {
|
|
username, password, err := getUsernameAndPassword(r)
|
|
if err != nil {
|
|
w.WriteHeader(libhttp.StatusBadRequest)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
|
|
if username == "" {
|
|
w.Header().Add("WWW-Authenticate", "Basic realm=\"influxdb\"")
|
|
w.Header().Add("Content-Type", "text/plain")
|
|
w.WriteHeader(libhttp.StatusUnauthorized)
|
|
w.Write([]byte(INVALID_CREDENTIALS_MSG))
|
|
return
|
|
}
|
|
|
|
user, err := self.userManager.AuthenticateClusterAdmin(username, password)
|
|
if err != nil {
|
|
w.Header().Add("WWW-Authenticate", "Basic realm=\"influxdb\"")
|
|
w.Header().Add("Content-Type", "text/plain")
|
|
w.WriteHeader(libhttp.StatusUnauthorized)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
statusCode, contentType, body := yieldUser(user, yield, isPretty(r))
|
|
if statusCode < 0 {
|
|
return
|
|
}
|
|
|
|
if statusCode == libhttp.StatusUnauthorized {
|
|
w.Header().Add("WWW-Authenticate", "Basic realm=\"influxdb\"")
|
|
}
|
|
w.Header().Add("content-type", contentType)
|
|
w.WriteHeader(statusCode)
|
|
if len(body) > 0 {
|
|
w.Write(body)
|
|
}
|
|
}
|
|
|
|
type NewUser struct {
|
|
Name string `json:"name"`
|
|
Password string `json:"password"`
|
|
IsAdmin bool `json:"isAdmin"`
|
|
ReadFrom string `json:"readFrom"`
|
|
WriteTo string `json:"writeTo"`
|
|
}
|
|
|
|
type UpdateClusterAdminUser struct {
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
type ApiUser struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
type UserDetail struct {
|
|
Name string `json:"name"`
|
|
IsAdmin bool `json:"isAdmin"`
|
|
WriteTo string `json:"writeTo"`
|
|
ReadFrom string `json:"readFrom"`
|
|
}
|
|
|
|
type ContinuousQuery struct {
|
|
Id int64 `json:"id"`
|
|
Query string `json:"query"`
|
|
}
|
|
|
|
type NewContinuousQuery struct {
|
|
Query string `json:"query"`
|
|
}
|
|
|
|
func (self *HTTPServer) listClusterAdmins(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
names, err := self.userManager.ListClusterAdmins(u)
|
|
if err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
users := make([]*ApiUser, 0, len(names))
|
|
for _, name := range names {
|
|
users = append(users, &ApiUser{name})
|
|
}
|
|
return libhttp.StatusOK, users
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) authenticateClusterAdmin(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
return libhttp.StatusOK, nil
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) createClusterAdmin(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
newUser := &NewUser{}
|
|
decoder := json.NewDecoder(r.Body)
|
|
err := decoder.Decode(newUser)
|
|
if err != nil {
|
|
w.WriteHeader(libhttp.StatusBadRequest)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
username := newUser.Name
|
|
if err := self.userManager.CreateClusterAdminUser(u, username, newUser.Password); err != nil {
|
|
errorStr := err.Error()
|
|
return errorToStatusCode(err), errorStr
|
|
}
|
|
return libhttp.StatusOK, nil
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) deleteClusterAdmin(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
newUser := r.URL.Query().Get(":user")
|
|
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
if err := self.userManager.DeleteClusterAdminUser(u, newUser); err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
return libhttp.StatusOK, nil
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) updateClusterAdmin(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
updateClusterAdminUser := &UpdateClusterAdminUser{}
|
|
decoder := json.NewDecoder(r.Body)
|
|
err := decoder.Decode(updateClusterAdminUser)
|
|
if err != nil {
|
|
w.WriteHeader(libhttp.StatusInternalServerError)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
|
|
newUser := r.URL.Query().Get(":user")
|
|
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
if err := self.userManager.ChangeClusterAdminPassword(u, newUser, updateClusterAdminUser.Password); err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
return libhttp.StatusOK, nil
|
|
})
|
|
}
|
|
|
|
// // db users management interface
|
|
|
|
func (self *HTTPServer) authenticateDbUser(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
code, body := self.tryAsDbUser(w, r, func(u User) (int, interface{}) {
|
|
return libhttp.StatusOK, nil
|
|
})
|
|
w.WriteHeader(code)
|
|
if len(body) > 0 {
|
|
w.Write(body)
|
|
}
|
|
}
|
|
|
|
func (self *HTTPServer) tryAsDbUser(w libhttp.ResponseWriter, r *libhttp.Request, yield func(User) (int, interface{})) (int, []byte) {
|
|
username, password, err := getUsernameAndPassword(r)
|
|
if err != nil {
|
|
return libhttp.StatusBadRequest, []byte(err.Error())
|
|
}
|
|
|
|
db := r.URL.Query().Get(":db")
|
|
|
|
if username == "" {
|
|
w.Header().Add("WWW-Authenticate", "Basic realm=\"influxdb\"")
|
|
return libhttp.StatusUnauthorized, []byte(INVALID_CREDENTIALS_MSG)
|
|
}
|
|
|
|
user, err := self.userManager.AuthenticateDbUser(db, username, password)
|
|
if err != nil {
|
|
w.Header().Add("WWW-Authenticate", "Basic realm=\"influxdb\"")
|
|
return libhttp.StatusUnauthorized, []byte(err.Error())
|
|
}
|
|
|
|
statusCode, contentType, v := yieldUser(user, yield, isPretty(r))
|
|
if statusCode == libhttp.StatusUnauthorized {
|
|
w.Header().Add("WWW-Authenticate", "Basic realm=\"influxdb\"")
|
|
}
|
|
w.Header().Add("content-type", contentType)
|
|
return statusCode, v
|
|
}
|
|
|
|
func (self *HTTPServer) tryAsDbUserAndClusterAdmin(w libhttp.ResponseWriter, r *libhttp.Request, yield func(User) (int, interface{})) {
|
|
log.Debug("Trying to auth as a db user")
|
|
statusCode, body := self.tryAsDbUser(w, r, yield)
|
|
if statusCode == libhttp.StatusUnauthorized {
|
|
log.Debug("Authenticating as a db user failed with %s (%d)", string(body), statusCode)
|
|
// tryAsDbUser will set this header, since we're retrying
|
|
// we should delete the header and let tryAsClusterAdmin
|
|
// set it properly
|
|
w.Header().Del("WWW-Authenticate")
|
|
self.tryAsClusterAdmin(w, r, yield)
|
|
return
|
|
}
|
|
|
|
if statusCode < 0 {
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(statusCode)
|
|
|
|
if len(body) > 0 {
|
|
w.Write(body)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (self *HTTPServer) listDbUsers(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
db := r.URL.Query().Get(":db")
|
|
|
|
self.tryAsDbUserAndClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
dbUsers, err := self.userManager.ListDbUsers(u, db)
|
|
if err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
|
|
users := make([]*UserDetail, 0, len(dbUsers))
|
|
for _, dbUser := range dbUsers {
|
|
users = append(users, &UserDetail{dbUser.GetName(), dbUser.IsDbAdmin(db), dbUser.GetWritePermission(), dbUser.GetReadPermission()})
|
|
}
|
|
return libhttp.StatusOK, users
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) showDbUser(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
db := r.URL.Query().Get(":db")
|
|
username := r.URL.Query().Get(":user")
|
|
|
|
self.tryAsDbUserAndClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
user, err := self.userManager.GetDbUser(u, db, username)
|
|
if err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
|
|
userDetail := &UserDetail{user.GetName(), user.IsDbAdmin(db), user.GetWritePermission(), user.GetReadPermission()}
|
|
|
|
return libhttp.StatusOK, userDetail
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) createDbUser(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
newUser := &NewUser{}
|
|
decoder := json.NewDecoder(r.Body)
|
|
err := decoder.Decode(newUser)
|
|
if err != nil {
|
|
w.WriteHeader(libhttp.StatusBadRequest)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
|
|
db := r.URL.Query().Get(":db")
|
|
|
|
self.tryAsDbUserAndClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
permissions := []string{}
|
|
if newUser.ReadFrom != "" || newUser.WriteTo != "" {
|
|
if newUser.ReadFrom == "" || newUser.WriteTo == "" {
|
|
return libhttp.StatusBadRequest, "You have to provide read and write permissions"
|
|
}
|
|
permissions = append(permissions, newUser.ReadFrom, newUser.WriteTo)
|
|
}
|
|
|
|
username := newUser.Name
|
|
if err := self.userManager.CreateDbUser(u, db, username, newUser.Password, permissions...); err != nil {
|
|
log.Error("Cannot create user: %s", err)
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
log.Debug("Created user %s", username)
|
|
if newUser.IsAdmin {
|
|
err = self.userManager.SetDbAdmin(u, db, newUser.Name, true)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
}
|
|
log.Debug("Successfully changed %s password", username)
|
|
return libhttp.StatusOK, nil
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) deleteDbUser(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
newUser := r.URL.Query().Get(":user")
|
|
db := r.URL.Query().Get(":db")
|
|
|
|
self.tryAsDbUserAndClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
if err := self.userManager.DeleteDbUser(u, db, newUser); err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
return libhttp.StatusOK, nil
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) updateDbUser(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
updateUser := make(map[string]interface{})
|
|
decoder := json.NewDecoder(r.Body)
|
|
err := decoder.Decode(&updateUser)
|
|
if err != nil {
|
|
w.WriteHeader(libhttp.StatusBadRequest)
|
|
w.Write([]byte(err.Error()))
|
|
return
|
|
}
|
|
|
|
newUser := r.URL.Query().Get(":user")
|
|
db := r.URL.Query().Get(":db")
|
|
|
|
self.tryAsDbUserAndClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
if pwd, ok := updateUser["password"]; ok {
|
|
newPassword, ok := pwd.(string)
|
|
if !ok {
|
|
return libhttp.StatusBadRequest, "password must be string"
|
|
}
|
|
|
|
if err := self.userManager.ChangeDbUserPassword(u, db, newUser, newPassword); err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
}
|
|
|
|
if readPermissions, ok := updateUser["readFrom"]; ok {
|
|
writePermissions, ok := updateUser["writeTo"]
|
|
if !ok {
|
|
return libhttp.StatusBadRequest, "Changing permissions requires passing readFrom and writeTo"
|
|
}
|
|
|
|
if err := self.userManager.ChangeDbUserPermissions(u, db, newUser, readPermissions.(string), writePermissions.(string)); err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
}
|
|
|
|
if admin, ok := updateUser["admin"]; ok {
|
|
isAdmin, ok := admin.(bool)
|
|
if !ok {
|
|
return libhttp.StatusBadRequest, "admin must be boolean"
|
|
}
|
|
|
|
if err := self.userManager.SetDbAdmin(u, db, newUser, isAdmin); err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
}
|
|
return libhttp.StatusOK, nil
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) ping(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
w.WriteHeader(libhttp.StatusOK)
|
|
w.Write([]byte("{\"status\":\"ok\"}"))
|
|
}
|
|
|
|
func (self *HTTPServer) listInterfaces(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
statusCode, contentType, body := yieldUser(nil, func(u User) (int, interface{}) {
|
|
entries, err := ioutil.ReadDir(filepath.Join(self.adminAssetsDir, "interfaces"))
|
|
|
|
if err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
|
|
directories := make([]string, 0, len(entries))
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
directories = append(directories, entry.Name())
|
|
}
|
|
}
|
|
return libhttp.StatusOK, directories
|
|
}, isPretty(r))
|
|
|
|
w.Header().Add("content-type", contentType)
|
|
w.WriteHeader(statusCode)
|
|
if len(body) > 0 {
|
|
w.Write(body)
|
|
}
|
|
}
|
|
|
|
func (self *HTTPServer) listServers(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
servers := self.clusterConfig.Servers()
|
|
serverMaps := make([]map[string]interface{}, len(servers), len(servers))
|
|
|
|
leaderRaftConnectString, _ := self.raftServer.GetLeaderRaftConnectString()
|
|
leaderRaftName := self.raftServer.GetLeaderRaftName()
|
|
for i, s := range servers {
|
|
serverMaps[i] = map[string]interface{}{
|
|
"id": s.Id,
|
|
"protobufConnectString": s.ProtobufConnectionString,
|
|
"isUp": s.IsUp(), //FIXME: IsUp is not consistent
|
|
"raftName": s.RaftName,
|
|
"state": s.State,
|
|
"stateName": s.GetStateName(),
|
|
"raftConnectionString": s.RaftConnectionString,
|
|
"leaderRaftName": leaderRaftName,
|
|
"leaderRaftConnectString": leaderRaftConnectString,
|
|
"isLeader": self.raftServer.IsLeaderByRaftName(s.RaftName)}
|
|
}
|
|
return libhttp.StatusOK, serverMaps
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) removeServers(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
id, err := strconv.ParseInt(r.URL.Query().Get(":id"), 10, 32)
|
|
if err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
|
|
err = self.raftServer.RemoveServer(uint32(id))
|
|
if err != nil {
|
|
return errorToStatusCode(err), err.Error()
|
|
}
|
|
err = self.dropServerShards(uint32(id))
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
return libhttp.StatusOK, nil
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) dropServerShards(serverId uint32) error {
|
|
shards := self.clusterConfig.GetShards()
|
|
for _, s := range shards {
|
|
for _, si := range s.ServerIds() {
|
|
if si == serverId {
|
|
err := self.raftServer.DropShard(uint32(s.Id()), []uint32{serverId})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type newShardInfo struct {
|
|
StartTime int64 `json:"startTime"`
|
|
EndTime int64 `json:"endTime"`
|
|
Shards []newShardServerIds `json:"shards"`
|
|
SpaceName string `json:"spaceName"`
|
|
Database string `json:"database"`
|
|
}
|
|
|
|
type newShardServerIds struct {
|
|
ServerIds []uint32 `json:"serverIds"`
|
|
}
|
|
|
|
func (self *HTTPServer) createShard(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
newShards := &newShardInfo{}
|
|
decoder := json.NewDecoder(r.Body)
|
|
err := decoder.Decode(&newShards)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
shards := make([]*cluster.NewShardData, 0)
|
|
|
|
for _, s := range newShards.Shards {
|
|
newShardData := &cluster.NewShardData{
|
|
StartTime: time.Unix(newShards.StartTime, 0),
|
|
EndTime: time.Unix(newShards.EndTime, 0),
|
|
ServerIds: s.ServerIds,
|
|
SpaceName: newShards.SpaceName,
|
|
Database: newShards.Database,
|
|
}
|
|
shards = append(shards, newShardData)
|
|
}
|
|
_, err = self.raftServer.CreateShards(shards)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
return libhttp.StatusAccepted, nil
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) getShards(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
shards := self.clusterConfig.GetShards()
|
|
shardMaps := make([]map[string]interface{}, 0, len(shards))
|
|
for _, s := range shards {
|
|
shardMaps = append(shardMaps, map[string]interface{}{
|
|
"id": s.Id(),
|
|
"endTime": s.EndTime().Unix(),
|
|
"startTime": s.StartTime().Unix(),
|
|
"serverIds": s.ServerIds(),
|
|
"spaceName": s.SpaceName,
|
|
"database": s.Database})
|
|
}
|
|
return libhttp.StatusOK, shardMaps
|
|
})
|
|
}
|
|
|
|
// Note: this is meant for testing purposes only and doesn't guarantee
|
|
// data integrity and shouldn't be used in client code.
|
|
func (self *HTTPServer) isInSync(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
if self.clusterConfig.HasUncommitedWrites() {
|
|
return 500, "false"
|
|
}
|
|
|
|
if !self.raftServer.CommittedAllChanges() {
|
|
return 500, "false"
|
|
}
|
|
|
|
return 200, "true"
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) dropShard(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
id, err := strconv.ParseInt(r.URL.Query().Get(":id"), 10, 64)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
serverIdInfo := &newShardServerIds{}
|
|
decoder := json.NewDecoder(r.Body)
|
|
err = decoder.Decode(&serverIdInfo)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
if len(serverIdInfo.ServerIds) < 1 {
|
|
return libhttp.StatusBadRequest, errors.New("Request must include an object with an array of 'serverIds'").Error()
|
|
}
|
|
|
|
err = self.raftServer.DropShard(uint32(id), serverIdInfo.ServerIds)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
return libhttp.StatusAccepted, nil
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) convertShardsToMap(shards []*cluster.ShardData) []interface{} {
|
|
result := make([]interface{}, 0)
|
|
for _, shard := range shards {
|
|
s := make(map[string]interface{})
|
|
s["id"] = shard.Id()
|
|
s["startTime"] = shard.StartTime().Unix()
|
|
s["endTime"] = shard.EndTime().Unix()
|
|
s["serverIds"] = shard.ServerIds()
|
|
result = append(result, s)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (self *HTTPServer) getShardSpaces(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
return libhttp.StatusOK, self.clusterConfig.GetShardSpaces()
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) createShardSpace(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
space := &cluster.ShardSpace{}
|
|
decoder := json.NewDecoder(r.Body)
|
|
err := decoder.Decode(space)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
space.Database = r.URL.Query().Get(":db")
|
|
err = space.Validate(self.clusterConfig, true)
|
|
if err != nil {
|
|
return libhttp.StatusBadRequest, err.Error()
|
|
}
|
|
err = self.raftServer.CreateShardSpace(space)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
return libhttp.StatusOK, nil
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) dropShardSpace(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
name := r.URL.Query().Get(":name")
|
|
db := r.URL.Query().Get(":db")
|
|
if err := self.raftServer.DropShardSpace(db, name); err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
return libhttp.StatusOK, nil
|
|
})
|
|
}
|
|
|
|
type DatabaseConfig struct {
|
|
Spaces []*cluster.ShardSpace `json:"spaces"`
|
|
ContinuousQueries []string `json:"continuousQueries"`
|
|
}
|
|
|
|
func (self *HTTPServer) configureDatabase(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
databaseConfig := &DatabaseConfig{}
|
|
decoder := json.NewDecoder(r.Body)
|
|
err := decoder.Decode(databaseConfig)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
database := r.URL.Query().Get(":db")
|
|
|
|
// validate before creating anything
|
|
for _, queryString := range databaseConfig.ContinuousQueries {
|
|
q, err := parser.ParseQuery(queryString)
|
|
if err != nil {
|
|
return libhttp.StatusBadRequest, err.Error()
|
|
}
|
|
for _, query := range q {
|
|
if !query.IsContinuousQuery() {
|
|
return libhttp.StatusBadRequest, fmt.Errorf("This query isn't a continuous query. Use 'into'. %s", query.GetQueryString())
|
|
}
|
|
}
|
|
}
|
|
|
|
// validate shard spaces
|
|
for _, space := range databaseConfig.Spaces {
|
|
err := space.Validate(self.clusterConfig, false)
|
|
if err != nil {
|
|
return libhttp.StatusBadRequest, err.Error()
|
|
}
|
|
}
|
|
|
|
err = self.coordinator.CreateDatabase(u, database)
|
|
if err != nil {
|
|
return libhttp.StatusBadRequest, err.Error()
|
|
}
|
|
for _, space := range databaseConfig.Spaces {
|
|
space.Database = database
|
|
err = self.raftServer.CreateShardSpace(space)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
}
|
|
for _, queryString := range databaseConfig.ContinuousQueries {
|
|
err := self.coordinator.RunQuery(u, database, queryString, cluster.NilProcessor{})
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
}
|
|
return libhttp.StatusCreated, nil
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) updateShardSpace(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
space := &cluster.ShardSpace{}
|
|
decoder := json.NewDecoder(r.Body)
|
|
err := decoder.Decode(space)
|
|
if err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
space.Database = r.URL.Query().Get(":db")
|
|
space.Name = r.URL.Query().Get(":name")
|
|
if !self.clusterConfig.DatabaseExists(space.Database) {
|
|
return libhttp.StatusNotAcceptable, "Can't update a shard space for a database that doesn't exist"
|
|
}
|
|
if !self.clusterConfig.ShardSpaceExists(space) {
|
|
return libhttp.StatusNotAcceptable, "Can't update a shard space that doesn't exist"
|
|
}
|
|
|
|
if err := self.raftServer.UpdateShardSpace(space); err != nil {
|
|
return libhttp.StatusInternalServerError, err.Error()
|
|
}
|
|
return libhttp.StatusOK, nil
|
|
})
|
|
}
|
|
|
|
func (self *HTTPServer) getClusterConfiguration(w libhttp.ResponseWriter, r *libhttp.Request) {
|
|
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
|
|
return libhttp.StatusOK, self.clusterConfig.SerializableConfiguration()
|
|
})
|
|
}
|
|
|
|
func HeaderHandler(handler libhttp.HandlerFunc, version string) libhttp.HandlerFunc {
|
|
return func(rw libhttp.ResponseWriter, req *libhttp.Request) {
|
|
rw.Header().Add("Access-Control-Allow-Origin", "*")
|
|
rw.Header().Add("Access-Control-Max-Age", "2592000")
|
|
rw.Header().Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
|
|
rw.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
|
|
rw.Header().Add("X-Influxdb-Version", version)
|
|
handler(rw, req)
|
|
}
|
|
}
|
|
|
|
func CompressionHeaderHandler(handler libhttp.HandlerFunc, version string) libhttp.HandlerFunc {
|
|
return HeaderHandler(CompressionHandler(true, handler), version)
|
|
}
|
|
|
|
type Flusher interface {
|
|
Flush() error
|
|
}
|
|
|
|
type CompressedResponseWriter struct {
|
|
responseWriter libhttp.ResponseWriter
|
|
writer io.Writer
|
|
compressionFlusher Flusher
|
|
responseFlusher libhttp.Flusher
|
|
}
|
|
|
|
func NewCompressionResponseWriter(useCompression bool, rw libhttp.ResponseWriter, req *libhttp.Request) *CompressedResponseWriter {
|
|
responseFlusher, _ := rw.(libhttp.Flusher)
|
|
|
|
if req.Header.Get("Accept-Encoding") != "" {
|
|
encodings := strings.Split(req.Header.Get("Accept-Encoding"), ",")
|
|
|
|
for _, val := range encodings {
|
|
if val == "gzip" {
|
|
rw.Header().Set("Content-Encoding", "gzip")
|
|
w, _ := gzip.NewWriterLevel(rw, gzip.BestSpeed)
|
|
return &CompressedResponseWriter{rw, w, w, responseFlusher}
|
|
} else if val == "deflate" {
|
|
rw.Header().Set("Content-Encoding", "deflate")
|
|
w, _ := zlib.NewWriterLevel(rw, zlib.BestSpeed)
|
|
return &CompressedResponseWriter{rw, w, w, responseFlusher}
|
|
}
|
|
}
|
|
}
|
|
|
|
return &CompressedResponseWriter{rw, rw, nil, responseFlusher}
|
|
}
|
|
|
|
func (self *CompressedResponseWriter) Header() libhttp.Header {
|
|
return self.responseWriter.Header()
|
|
}
|
|
|
|
func (self *CompressedResponseWriter) Write(bs []byte) (int, error) {
|
|
return self.writer.Write(bs)
|
|
}
|
|
|
|
func (self *CompressedResponseWriter) Flush() {
|
|
if self.compressionFlusher != nil {
|
|
self.compressionFlusher.Flush()
|
|
}
|
|
|
|
if self.responseFlusher != nil {
|
|
self.responseFlusher.Flush()
|
|
}
|
|
}
|
|
|
|
func (self *CompressedResponseWriter) WriteHeader(responseCode int) {
|
|
self.responseWriter.WriteHeader(responseCode)
|
|
}
|
|
|
|
func CompressionHandler(enableCompression bool, handler libhttp.HandlerFunc) libhttp.HandlerFunc {
|
|
if !enableCompression {
|
|
return handler
|
|
}
|
|
|
|
return func(rw libhttp.ResponseWriter, req *libhttp.Request) {
|
|
crw := NewCompressionResponseWriter(true, rw, req)
|
|
handler(crw, req)
|
|
switch x := crw.writer.(type) {
|
|
case *gzip.Writer:
|
|
x.Close()
|
|
case *zlib.Writer:
|
|
x.Close()
|
|
}
|
|
}
|
|
}
|
|
|
|
type SeriesWriter struct {
|
|
yield func(*protocol.Series) error
|
|
}
|
|
|
|
func NewSeriesWriter(yield func(*protocol.Series) error) *SeriesWriter {
|
|
return &SeriesWriter{yield}
|
|
}
|
|
|
|
func (self *SeriesWriter) Yield(series *protocol.Series) (bool, error) {
|
|
err := self.yield(series)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (self *SeriesWriter) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (self *SeriesWriter) Name() string {
|
|
return "SeriesWriter"
|
|
}
|
|
*/
|