1332 lines
33 KiB
Go
1332 lines
33 KiB
Go
package influxdb
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"code.google.com/p/go.crypto/bcrypt"
|
|
"github.com/influxdb/influxdb/messaging"
|
|
)
|
|
|
|
const (
|
|
// DefaultRootPassword is the password initially set for the root user.
|
|
// It is also used when reseting the root user's password.
|
|
DefaultRootPassword = "root"
|
|
|
|
// DefaultRetentionPolicyName is the name of a databases's default shard space.
|
|
DefaultRetentionPolicyName = "default"
|
|
|
|
// DefaultSplitN represents the number of partitions a shard is split into.
|
|
DefaultSplitN = 1
|
|
|
|
// DefaultReplicaN represents the number of replicas data is written to.
|
|
DefaultReplicaN = 1
|
|
|
|
// DefaultShardDuration is the time period held by a shard.
|
|
DefaultShardDuration = 7 * (24 * time.Hour)
|
|
|
|
// DefaultShardRetention is the length of time before a shard is dropped.
|
|
DefaultShardRetention = time.Duration(0)
|
|
)
|
|
|
|
const (
|
|
// Data node messages
|
|
createDataNodeMessageType = messaging.MessageType(0x00)
|
|
deleteDataNodeMessageType = messaging.MessageType(0x01)
|
|
|
|
// Database messages
|
|
createDatabaseMessageType = messaging.MessageType(0x10)
|
|
deleteDatabaseMessageType = messaging.MessageType(0x11)
|
|
|
|
// Retention policy messages
|
|
createRetentionPolicyMessageType = messaging.MessageType(0x20)
|
|
updateRetentionPolicyMessageType = messaging.MessageType(0x21)
|
|
deleteRetentionPolicyMessageType = messaging.MessageType(0x22)
|
|
setDefaultRetentionPolicyMessageType = messaging.MessageType(0x23)
|
|
|
|
// User messages
|
|
createUserMessageType = messaging.MessageType(0x30)
|
|
updateUserMessageType = messaging.MessageType(0x31)
|
|
deleteUserMessageType = messaging.MessageType(0x32)
|
|
|
|
// Shard messages
|
|
createShardIfNotExistsMessageType = messaging.MessageType(0x40)
|
|
|
|
// Series messages
|
|
createSeriesIfNotExistsMessageType = messaging.MessageType(0x50)
|
|
|
|
// Write raw data messages (per-topic)
|
|
writeSeriesMessageType = messaging.MessageType(0x80)
|
|
)
|
|
|
|
// Server represents a collection of metadata and raw metric data.
|
|
type Server struct {
|
|
mu sync.RWMutex
|
|
id uint64
|
|
path string
|
|
done chan struct{} // goroutine close notification
|
|
|
|
client MessagingClient // broker client
|
|
index uint64 // highest broadcast index seen
|
|
errors map[uint64]error // message errors
|
|
|
|
meta *metastore // metadata store
|
|
|
|
dataNodes map[uint64]*DataNode // data nodes by id
|
|
|
|
databases map[string]*database // databases by name
|
|
databasesByShard map[uint64]*database // databases by shard id
|
|
users map[string]*User // user by name
|
|
}
|
|
|
|
// NewServer returns a new instance of Server.
|
|
func NewServer() *Server {
|
|
return &Server{
|
|
meta: &metastore{},
|
|
dataNodes: make(map[uint64]*DataNode),
|
|
databases: make(map[string]*database),
|
|
databasesByShard: make(map[uint64]*database),
|
|
users: make(map[string]*User),
|
|
errors: make(map[uint64]error),
|
|
}
|
|
}
|
|
|
|
// ID returns the data node id for the server.
|
|
// Returns zero if the server is closed or the server has not joined a cluster.
|
|
func (s *Server) ID() uint64 {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.id
|
|
}
|
|
|
|
// Path returns the path used when opening the server.
|
|
// Returns an empty string when the server is closed.
|
|
func (s *Server) Path() string {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.path
|
|
}
|
|
|
|
// shardPath returns the path for a shard.
|
|
func (s *Server) shardPath(id uint64) string {
|
|
if s.path == "" {
|
|
return ""
|
|
}
|
|
return filepath.Join(s.path, "shards", strconv.FormatUint(id, 10))
|
|
}
|
|
|
|
// Open initializes the server from a given path.
|
|
func (s *Server) Open(path string) error {
|
|
// Ensure the server isn't already open and there's a path provided.
|
|
if s.opened() {
|
|
return ErrServerOpen
|
|
} else if path == "" {
|
|
return ErrPathRequired
|
|
}
|
|
|
|
// Create required directories.
|
|
if err := os.MkdirAll(path, 0700); err != nil {
|
|
return err
|
|
}
|
|
if err := os.MkdirAll(filepath.Join(path, "shards"), 0700); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Open metadata store.
|
|
if err := s.meta.open(filepath.Join(path, "meta")); err != nil {
|
|
return fmt.Errorf("meta: %s", err)
|
|
}
|
|
|
|
// Load state from metastore.
|
|
if err := s.load(); err != nil {
|
|
return fmt.Errorf("load: %s", err)
|
|
}
|
|
|
|
// Set the server path.
|
|
s.path = path
|
|
|
|
return nil
|
|
}
|
|
|
|
// opened returns true when the server is open.
|
|
func (s *Server) opened() bool { return s.path != "" }
|
|
|
|
// Close shuts down the server.
|
|
func (s *Server) Close() error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if !s.opened() {
|
|
return ErrServerClosed
|
|
}
|
|
|
|
// Close message processing.
|
|
s.setClient(nil)
|
|
|
|
// Close metastore.
|
|
_ = s.meta.close()
|
|
|
|
// Remove path.
|
|
s.path = ""
|
|
|
|
return nil
|
|
}
|
|
|
|
// load reads the state of the server from the metastore.
|
|
func (s *Server) load() error {
|
|
return s.meta.view(func(tx *metatx) error {
|
|
// Read server id.
|
|
s.id = tx.id()
|
|
|
|
// Load databases.
|
|
s.databases = make(map[string]*database)
|
|
for _, db := range tx.databases() {
|
|
s.databases[db.name] = db
|
|
|
|
for sh := range db.shards {
|
|
s.databasesByShard[sh] = db
|
|
}
|
|
|
|
// load the index
|
|
log.Printf("Loading metadata index for %s\n", db.name)
|
|
err := s.meta.view(func(tx *metatx) error {
|
|
tx.indexDatabase(db)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Load users.
|
|
s.users = make(map[string]*User)
|
|
for _, u := range tx.users() {
|
|
s.users[u.Name] = u
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// Client retrieves the current messaging client.
|
|
func (s *Server) Client() MessagingClient {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.client
|
|
}
|
|
|
|
// SetClient sets the messaging client on the server.
|
|
func (s *Server) SetClient(client MessagingClient) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.setClient(client)
|
|
}
|
|
|
|
func (s *Server) setClient(client MessagingClient) error {
|
|
// Ensure the server is open.
|
|
if !s.opened() {
|
|
return ErrServerClosed
|
|
}
|
|
|
|
// Stop previous processor, if running.
|
|
if s.done != nil {
|
|
close(s.done)
|
|
s.done = nil
|
|
}
|
|
|
|
// Set the messaging client.
|
|
s.client = client
|
|
|
|
// Start goroutine to read messages from the broker.
|
|
if client != nil {
|
|
s.done = make(chan struct{}, 0)
|
|
go s.processor(client, s.done)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// broadcast encodes a message as JSON and send it to the broker's broadcast topic.
|
|
// This function waits until the message has been processed by the server.
|
|
// Returns the broker log index of the message or an error.
|
|
func (s *Server) broadcast(typ messaging.MessageType, c interface{}) (uint64, error) {
|
|
// Encode the command.
|
|
data, err := json.Marshal(c)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Publish the message.
|
|
m := &messaging.Message{
|
|
Type: typ,
|
|
TopicID: messaging.BroadcastTopicID,
|
|
Data: data,
|
|
}
|
|
index, err := s.client.Publish(m)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Wait for the server to receive the message.
|
|
err = s.sync(index)
|
|
|
|
return index, err
|
|
}
|
|
|
|
// sync blocks until a given index (or a higher index) has been seen.
|
|
// Returns any error associated with the command.
|
|
func (s *Server) sync(index uint64) error {
|
|
for {
|
|
// Check if index has occurred. If so, retrieve the error and return.
|
|
s.mu.RLock()
|
|
if s.index >= index {
|
|
err, ok := s.errors[index]
|
|
if ok {
|
|
delete(s.errors, index)
|
|
}
|
|
s.mu.RUnlock()
|
|
return err
|
|
}
|
|
s.mu.RUnlock()
|
|
|
|
// Otherwise wait momentarily and check again.
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
// Initialize creates a new data node and initializes the server's id to 1.
|
|
func (s *Server) Initialize(u *url.URL) error {
|
|
// Create a new data node.
|
|
if err := s.CreateDataNode(u); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Ensure the data node returns with an ID of 1.
|
|
// If it doesn't then something went really wrong. We have to panic because
|
|
// the messaging client relies on the first server being assigned ID 1.
|
|
n := s.DataNodeByURL(u)
|
|
assert(n != nil && n.ID == 1, "invalid initial server id: %d", n.ID)
|
|
|
|
// Set the ID on the metastore.
|
|
if err := s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.setID(n.ID)
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set the ID on the server.
|
|
s.id = 1
|
|
|
|
return nil
|
|
}
|
|
|
|
// DataNode returns a data node by id.
|
|
func (s *Server) DataNode(id uint64) *DataNode {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.dataNodes[id]
|
|
}
|
|
|
|
// DataNodeByURL returns a data node by url.
|
|
func (s *Server) DataNodeByURL(u *url.URL) *DataNode {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
for _, n := range s.dataNodes {
|
|
if n.URL.String() == u.String() {
|
|
return n
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DataNodes returns a list of data nodes.
|
|
func (s *Server) DataNodes() (a []*DataNode) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
for _, n := range s.dataNodes {
|
|
a = append(a, n)
|
|
}
|
|
sort.Sort(dataNodes(a))
|
|
return
|
|
}
|
|
|
|
// CreateDataNode creates a new data node with a given URL.
|
|
func (s *Server) CreateDataNode(u *url.URL) error {
|
|
c := &createDataNodeCommand{URL: u.String()}
|
|
_, err := s.broadcast(createDataNodeMessageType, c)
|
|
return err
|
|
}
|
|
|
|
func (s *Server) applyCreateDataNode(m *messaging.Message) (err error) {
|
|
var c createDataNodeCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Validate parameters.
|
|
if c.URL == "" {
|
|
return ErrDataNodeURLRequired
|
|
}
|
|
|
|
// Check that another node with the same URL doesn't already exist.
|
|
u, _ := url.Parse(c.URL)
|
|
for _, n := range s.dataNodes {
|
|
if n.URL.String() == u.String() {
|
|
return ErrDataNodeExists
|
|
}
|
|
}
|
|
|
|
// Create data node.
|
|
n := newDataNode()
|
|
n.URL = u
|
|
|
|
// Persist to metastore.
|
|
err = s.meta.mustUpdate(func(tx *metatx) error {
|
|
n.ID = tx.nextDataNodeID()
|
|
return tx.saveDataNode(n)
|
|
})
|
|
|
|
// Add to node on server.
|
|
s.dataNodes[n.ID] = n
|
|
|
|
return
|
|
}
|
|
|
|
type createDataNodeCommand struct {
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// DeleteDataNode deletes an existing data node.
|
|
func (s *Server) DeleteDataNode(id uint64) error {
|
|
c := &deleteDataNodeCommand{ID: id}
|
|
_, err := s.broadcast(deleteDataNodeMessageType, c)
|
|
return err
|
|
}
|
|
|
|
func (s *Server) applyDeleteDataNode(m *messaging.Message) (err error) {
|
|
var c deleteDataNodeCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
n := s.dataNodes[c.ID]
|
|
if n == nil {
|
|
return ErrDataNodeNotFound
|
|
}
|
|
|
|
// Remove from metastore.
|
|
err = s.meta.mustUpdate(func(tx *metatx) error { return tx.deleteDataNode(c.ID) })
|
|
|
|
// Delete the node.
|
|
delete(s.dataNodes, n.ID)
|
|
|
|
return
|
|
}
|
|
|
|
type deleteDataNodeCommand struct {
|
|
ID uint64 `json:"id"`
|
|
}
|
|
|
|
// DatabaseExists returns true if a database exists.
|
|
func (s *Server) DatabaseExists(name string) bool {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.databases[name] != nil
|
|
}
|
|
|
|
// Databases returns a sorted list of all database names.
|
|
func (s *Server) Databases() (a []string) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
for _, db := range s.databases {
|
|
a = append(a, db.name)
|
|
}
|
|
sort.Strings(a)
|
|
return
|
|
}
|
|
|
|
// CreateDatabase creates a new database.
|
|
func (s *Server) CreateDatabase(name string) error {
|
|
c := &createDatabaseCommand{Name: name}
|
|
_, err := s.broadcast(createDatabaseMessageType, c)
|
|
return err
|
|
}
|
|
|
|
func (s *Server) applyCreateDatabase(m *messaging.Message) (err error) {
|
|
var c createDatabaseCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if s.databases[c.Name] != nil {
|
|
return ErrDatabaseExists
|
|
}
|
|
|
|
// Create database entry.
|
|
db := newDatabase()
|
|
db.name = c.Name
|
|
|
|
// Persist to metastore.
|
|
err = s.meta.mustUpdate(func(tx *metatx) error { return tx.saveDatabase(db) })
|
|
|
|
// Add to databases on server.
|
|
s.databases[c.Name] = db
|
|
|
|
return
|
|
}
|
|
|
|
type createDatabaseCommand struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// DeleteDatabase deletes an existing database.
|
|
func (s *Server) DeleteDatabase(name string) error {
|
|
c := &deleteDatabaseCommand{Name: name}
|
|
_, err := s.broadcast(deleteDatabaseMessageType, c)
|
|
return err
|
|
}
|
|
|
|
func (s *Server) applyDeleteDatabase(m *messaging.Message) (err error) {
|
|
var c deleteDatabaseCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if s.databases[c.Name] == nil {
|
|
return ErrDatabaseNotFound
|
|
}
|
|
|
|
// Remove from metastore.
|
|
err = s.meta.mustUpdate(func(tx *metatx) error { return tx.deleteDatabase(c.Name) })
|
|
|
|
// Delete the database entry.
|
|
delete(s.databases, c.Name)
|
|
return
|
|
}
|
|
|
|
type deleteDatabaseCommand struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// shardByTimestamp returns a shard that owns a given timestamp for a database.
|
|
func (s *Server) shardByTimestamp(database, policy string, id uint32, timestamp time.Time) (*Shard, error) {
|
|
db := s.databases[database]
|
|
if db == nil {
|
|
return nil, ErrDatabaseNotFound
|
|
}
|
|
return db.shardByTimestamp(policy, id, timestamp)
|
|
}
|
|
|
|
// Shards returns a list of all shards for a database.
|
|
// Returns an error if the database doesn't exist.
|
|
func (s *Server) Shards(database string) ([]*Shard, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
// Lookup database.
|
|
db := s.databases[database]
|
|
if db == nil {
|
|
return nil, ErrDatabaseNotFound
|
|
}
|
|
|
|
// Retrieve shards from database.
|
|
shards := make([]*Shard, 0, len(db.shards))
|
|
for _, shard := range db.shards {
|
|
shards = append(shards, shard)
|
|
}
|
|
return shards, nil
|
|
}
|
|
|
|
// shardsByTimestamp returns all shards that own a given timestamp for a database.
|
|
func (s *Server) shardsByTimestamp(database, policy string, timestamp time.Time) ([]*Shard, error) {
|
|
db := s.databases[database]
|
|
if db == nil {
|
|
return nil, ErrDatabaseNotFound
|
|
}
|
|
return db.shardsByTimestamp(policy, timestamp)
|
|
}
|
|
|
|
// CreateShardsIfNotExist creates all the shards for a retention policy for the interval a timestamp falls into.
|
|
// Note that multiple shards can be created for each bucket of time.
|
|
func (s *Server) CreateShardsIfNotExists(database, policy string, timestamp time.Time) error {
|
|
c := &createShardIfNotExistsCommand{Database: database, Policy: policy, Timestamp: timestamp}
|
|
_, err := s.broadcast(createShardIfNotExistsMessageType, c)
|
|
return err
|
|
}
|
|
|
|
// createShardIfNotExists returns the shard for a given retention policy, series, and timestamp.
|
|
// If it doesn't exist, it will create all shards for the given timestamp
|
|
func (s *Server) createShardIfNotExists(database, policy string, id uint32, timestamp time.Time) (*Shard, error) {
|
|
// Check if shard exists first.
|
|
sh, err := s.shardByTimestamp(database, policy, id, timestamp)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if sh != nil {
|
|
return sh, nil
|
|
}
|
|
|
|
// If the shard doesn't exist then create it.
|
|
if err := s.CreateShardsIfNotExists(database, policy, timestamp); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Lookup the shard again.
|
|
return s.shardByTimestamp(database, policy, id, timestamp)
|
|
}
|
|
|
|
func (s *Server) applyCreateShardIfNotExists(m *messaging.Message) (err error) {
|
|
var c createShardIfNotExistsCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Retrieve database.
|
|
db := s.databases[c.Database]
|
|
if s.databases[c.Database] == nil {
|
|
return ErrDatabaseNotFound
|
|
}
|
|
|
|
// Validate retention policy.
|
|
rp := db.policies[c.Policy]
|
|
if rp == nil {
|
|
return ErrRetentionPolicyNotFound
|
|
}
|
|
|
|
// If we can match to an existing shard date range then just ignore request.
|
|
for _, sh := range rp.Shards {
|
|
if timeBetweenInclusive(c.Timestamp, sh.StartTime, sh.EndTime) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// If no shards match then create a new one.
|
|
sh := newShard()
|
|
sh.ID = m.Index
|
|
sh.StartTime = c.Timestamp.Truncate(rp.Duration).UTC()
|
|
sh.EndTime = sh.StartTime.Add(rp.Duration).UTC()
|
|
|
|
// Open shard.
|
|
if err := sh.open(s.shardPath(sh.ID)); err != nil {
|
|
panic("unable to open shard: " + err.Error())
|
|
}
|
|
|
|
// Persist to metastore if a shard was created.
|
|
if err = s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveDatabase(db)
|
|
}); err != nil {
|
|
_ = sh.close()
|
|
return
|
|
}
|
|
|
|
// Add to lookups.
|
|
s.databasesByShard[sh.ID] = db
|
|
db.shards[sh.ID] = sh
|
|
rp.Shards = append(rp.Shards, sh)
|
|
|
|
// TODO: Subscribe to shard if it matches the server's index.
|
|
|
|
return
|
|
}
|
|
|
|
type createShardIfNotExistsCommand struct {
|
|
Database string `json:"name"`
|
|
Policy string `json:"policy"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// User returns a user by username
|
|
// Returns nil if the user does not exist.
|
|
func (s *Server) User(name string) *User {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.users[name]
|
|
}
|
|
|
|
// Users returns a list of all users, sorted by name.
|
|
func (s *Server) Users() (a []*User) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
for _, u := range s.users {
|
|
a = append(a, u)
|
|
}
|
|
sort.Sort(users(a))
|
|
return a
|
|
}
|
|
|
|
// AdminUserExists returns whether at least 1 admin-level user exists.
|
|
func (s *Server) AdminUserExists() bool {
|
|
for _, u := range s.users {
|
|
if u.Admin {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Authenticate returns an authenticated user by username. If any error occurs,
|
|
// or the authentication credentials are invalid, an error is returned.
|
|
func (s *Server) Authenticate(username, password string) (*User, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
u := s.users[username]
|
|
if u == nil {
|
|
return nil, fmt.Errorf("user not found")
|
|
}
|
|
err := u.Authenticate(password)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid credentials")
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
// CreateUser creates a user on the server.
|
|
func (s *Server) CreateUser(username, password string, admin bool) error {
|
|
c := &createUserCommand{Username: username, Password: password, Admin: admin}
|
|
_, err := s.broadcast(createUserMessageType, c)
|
|
return err
|
|
}
|
|
|
|
func (s *Server) applyCreateUser(m *messaging.Message) (err error) {
|
|
var c createUserCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Validate user.
|
|
if c.Username == "" {
|
|
return ErrUsernameRequired
|
|
} else if s.users[c.Username] != nil {
|
|
return ErrUserExists
|
|
}
|
|
|
|
// Generate the hash of the password.
|
|
hash, err := HashPassword(c.Password)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create the user.
|
|
u := &User{
|
|
Name: c.Username,
|
|
Hash: string(hash),
|
|
Admin: c.Admin,
|
|
}
|
|
|
|
// Persist to metastore.
|
|
err = s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveUser(u)
|
|
})
|
|
|
|
s.users[u.Name] = u
|
|
return
|
|
}
|
|
|
|
type createUserCommand struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
Admin bool `json:"admin,omitempty"`
|
|
}
|
|
|
|
// UpdateUser updates an existing user on the server.
|
|
func (s *Server) UpdateUser(username, password string) error {
|
|
c := &updateUserCommand{Username: username, Password: password}
|
|
_, err := s.broadcast(updateUserMessageType, c)
|
|
return err
|
|
}
|
|
|
|
func (s *Server) applyUpdateUser(m *messaging.Message) (err error) {
|
|
var c updateUserCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Validate command.
|
|
u := s.users[c.Username]
|
|
if u == nil {
|
|
return ErrUserNotFound
|
|
}
|
|
|
|
// Update the user's password, if set.
|
|
if c.Password != "" {
|
|
hash, err := HashPassword(c.Password)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u.Hash = string(hash)
|
|
}
|
|
|
|
// Persist to metastore.
|
|
return s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveUser(u)
|
|
})
|
|
}
|
|
|
|
type updateUserCommand struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password,omitempty"`
|
|
}
|
|
|
|
// DeleteUser removes a user from the server.
|
|
func (s *Server) DeleteUser(username string) error {
|
|
c := &deleteUserCommand{Username: username}
|
|
_, err := s.broadcast(deleteUserMessageType, c)
|
|
return err
|
|
}
|
|
|
|
func (s *Server) applyDeleteUser(m *messaging.Message) error {
|
|
var c deleteUserCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Validate user.
|
|
if c.Username == "" {
|
|
return ErrUsernameRequired
|
|
} else if s.users[c.Username] == nil {
|
|
return ErrUserNotFound
|
|
}
|
|
|
|
// Remove from metastore.
|
|
s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.deleteUser(c.Username)
|
|
})
|
|
|
|
// Delete the user.
|
|
delete(s.users, c.Username)
|
|
return nil
|
|
}
|
|
|
|
type deleteUserCommand struct {
|
|
Username string `json:"username"`
|
|
}
|
|
|
|
// RetentionPolicy returns a retention policy by name.
|
|
// Returns an error if the database doesn't exist.
|
|
func (s *Server) RetentionPolicy(database, name string) (*RetentionPolicy, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Lookup database.
|
|
db := s.databases[database]
|
|
if db == nil {
|
|
return nil, ErrDatabaseNotFound
|
|
}
|
|
|
|
return db.policies[name], nil
|
|
}
|
|
|
|
// DefaultRetentionPolicy returns the default retention policy for a database.
|
|
// Returns an error if the database doesn't exist.
|
|
func (s *Server) DefaultRetentionPolicy(database string) (*RetentionPolicy, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Lookup database.
|
|
db := s.databases[database]
|
|
if db == nil {
|
|
return nil, ErrDatabaseNotFound
|
|
}
|
|
|
|
return db.policies[db.defaultRetentionPolicy], nil
|
|
}
|
|
|
|
// RetentionPolicies returns a list of retention polocies for a database.
|
|
// Returns an error if the database doesn't exist.
|
|
func (s *Server) RetentionPolicies(database string) ([]*RetentionPolicy, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
// Lookup database.
|
|
db := s.databases[database]
|
|
if db == nil {
|
|
return nil, ErrDatabaseNotFound
|
|
}
|
|
|
|
// Retrieve the policies.
|
|
a := make([]*RetentionPolicy, 0, len(db.policies))
|
|
for _, p := range db.policies {
|
|
a = append(a, p)
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
// CreateRetentionPolicy creates a retention policy for a database.
|
|
func (s *Server) CreateRetentionPolicy(database string, rp *RetentionPolicy) error {
|
|
c := &createRetentionPolicyCommand{
|
|
Database: database,
|
|
Name: rp.Name,
|
|
Duration: rp.Duration,
|
|
ReplicaN: rp.ReplicaN,
|
|
SplitN: rp.SplitN,
|
|
}
|
|
_, err := s.broadcast(createRetentionPolicyMessageType, c)
|
|
return err
|
|
}
|
|
|
|
func (s *Server) applyCreateRetentionPolicy(m *messaging.Message) error {
|
|
var c createRetentionPolicyCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Retrieve the database.
|
|
db := s.databases[c.Database]
|
|
if s.databases[c.Database] == nil {
|
|
return ErrDatabaseNotFound
|
|
} else if c.Name == "" {
|
|
return ErrRetentionPolicyNameRequired
|
|
} else if db.policies[c.Name] != nil {
|
|
return ErrRetentionPolicyExists
|
|
}
|
|
|
|
// Add policy to the database.
|
|
db.policies[c.Name] = &RetentionPolicy{
|
|
Name: c.Name,
|
|
Duration: c.Duration,
|
|
ReplicaN: c.ReplicaN,
|
|
SplitN: c.SplitN,
|
|
}
|
|
|
|
// Persist to metastore.
|
|
s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveDatabase(db)
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
type createRetentionPolicyCommand struct {
|
|
Database string `json:"database"`
|
|
Name string `json:"name"`
|
|
Duration time.Duration `json:"duration"`
|
|
ReplicaN uint32 `json:"replicaN"`
|
|
SplitN uint32 `json:"splitN"`
|
|
}
|
|
|
|
// UpdateRetentionPolicy updates an existing retention policy on a database.
|
|
func (s *Server) UpdateRetentionPolicy(database, name string, rp *RetentionPolicy) error {
|
|
c := &updateRetentionPolicyCommand{Database: database, Name: name, NewName: rp.Name}
|
|
_, err := s.broadcast(updateRetentionPolicyMessageType, c)
|
|
return err
|
|
}
|
|
|
|
type updateRetentionPolicyCommand struct {
|
|
Database string `json:"database"`
|
|
Name string `json:"name"`
|
|
NewName string `json:"newName"`
|
|
}
|
|
|
|
func (s *Server) applyUpdateRetentionPolicy(m *messaging.Message) (err error) {
|
|
var c updateRetentionPolicyCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Validate command.
|
|
db := s.databases[c.Database]
|
|
if s.databases[c.Database] == nil {
|
|
return ErrDatabaseNotFound
|
|
} else if c.Name == "" {
|
|
return ErrRetentionPolicyNameRequired
|
|
}
|
|
|
|
// Retrieve the policy.
|
|
p := db.policies[c.Name]
|
|
if db.policies[c.Name] == nil {
|
|
return ErrRetentionPolicyNotFound
|
|
}
|
|
|
|
// Update the policy name, if not blank.
|
|
if c.NewName != c.Name && c.NewName != "" {
|
|
delete(db.policies, p.Name)
|
|
p.Name = c.NewName
|
|
db.policies[p.Name] = p
|
|
}
|
|
|
|
// Persist to metastore.
|
|
err = s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveDatabase(db)
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
// DeleteRetentionPolicy removes a retention policy from a database.
|
|
func (s *Server) DeleteRetentionPolicy(database, name string) error {
|
|
c := &deleteRetentionPolicyCommand{Database: database, Name: name}
|
|
_, err := s.broadcast(deleteRetentionPolicyMessageType, c)
|
|
return err
|
|
}
|
|
|
|
func (s *Server) applyDeleteRetentionPolicy(m *messaging.Message) (err error) {
|
|
var c deleteRetentionPolicyCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Retrieve the database.
|
|
db := s.databases[c.Database]
|
|
if s.databases[c.Database] == nil {
|
|
return ErrDatabaseNotFound
|
|
} else if c.Name == "" {
|
|
return ErrRetentionPolicyNameRequired
|
|
} else if db.policies[c.Name] == nil {
|
|
return ErrRetentionPolicyNotFound
|
|
}
|
|
|
|
// Remove retention policy.
|
|
delete(db.policies, c.Name)
|
|
|
|
// Persist to metastore.
|
|
err = s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveDatabase(db)
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
type deleteRetentionPolicyCommand struct {
|
|
Database string `json:"database"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// SetDefaultRetentionPolicy sets the default policy to write data into and query from on a database.
|
|
func (s *Server) SetDefaultRetentionPolicy(database, name string) error {
|
|
c := &setDefaultRetentionPolicyCommand{Database: database, Name: name}
|
|
_, err := s.broadcast(setDefaultRetentionPolicyMessageType, c)
|
|
return err
|
|
}
|
|
|
|
func (s *Server) applySetDefaultRetentionPolicy(m *messaging.Message) (err error) {
|
|
var c setDefaultRetentionPolicyCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Validate command.
|
|
db := s.databases[c.Database]
|
|
if s.databases[c.Database] == nil {
|
|
return ErrDatabaseNotFound
|
|
} else if db.policies[c.Name] == nil {
|
|
return ErrRetentionPolicyNotFound
|
|
}
|
|
|
|
// Update default policy.
|
|
db.defaultRetentionPolicy = c.Name
|
|
|
|
// Persist to metastore.
|
|
err = s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveDatabase(db)
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
type setDefaultRetentionPolicyCommand struct {
|
|
Database string `json:"database"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func (s *Server) applyCreateSeriesIfNotExists(m *messaging.Message) error {
|
|
var c createSeriesIfNotExistsCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Validate command.
|
|
db := s.databases[c.Database]
|
|
if db == nil {
|
|
return ErrDatabaseNotFound
|
|
}
|
|
|
|
if _, series := db.MeasurementAndSeries(c.Name, c.Tags); series != nil {
|
|
return nil
|
|
}
|
|
|
|
// save to the metastore and add it to the in memory index
|
|
var series *Series
|
|
err := s.meta.mustUpdate(func(tx *metatx) error {
|
|
var err error
|
|
series, err = tx.createSeries(db.name, c.Name, c.Tags)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
db.addSeriesToIndex(c.Name, series)
|
|
|
|
return nil
|
|
}
|
|
|
|
type createSeriesIfNotExistsCommand struct {
|
|
Database string `json:"database"`
|
|
Name string `json:"name"`
|
|
Tags map[string]string `json:"tags"`
|
|
}
|
|
|
|
// WriteSeries writes series data to the database.
|
|
func (s *Server) WriteSeries(database, retentionPolicy, name string, tags map[string]string, timestamp time.Time, values map[string]interface{}) error {
|
|
// Find the id for the series and tagset
|
|
id, err := s.createSeriesIfNotExists(database, name, tags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// If the retention policy is not set, use the default for this database.
|
|
if retentionPolicy == "" {
|
|
rp, err := s.DefaultRetentionPolicy(database)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to determine default retention policy", err.Error())
|
|
}
|
|
retentionPolicy = rp.Name
|
|
}
|
|
|
|
// Now write it into the shard.
|
|
sh, err := s.createShardIfNotExists(database, retentionPolicy, id, timestamp)
|
|
if err != nil {
|
|
return fmt.Errorf("create shard(%s/%s): %s", retentionPolicy, timestamp.Format(time.RFC3339Nano), err)
|
|
}
|
|
|
|
// Encode point to a byte slice.
|
|
data, err := marshalPoint(id, timestamp, values)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Publish "write series" message on shard's topic to broker.
|
|
m := &messaging.Message{
|
|
Type: writeSeriesMessageType,
|
|
TopicID: sh.ID,
|
|
Data: data,
|
|
}
|
|
|
|
_, err = s.client.Publish(m)
|
|
return err
|
|
}
|
|
|
|
func (s *Server) applyWriteSeries(m *messaging.Message) error {
|
|
s.mu.RLock()
|
|
|
|
// Retrieve the database.
|
|
db := s.databasesByShard[m.TopicID]
|
|
if db == nil {
|
|
s.mu.RUnlock()
|
|
return ErrDatabaseNotFound
|
|
}
|
|
|
|
// Retrieve the shard.
|
|
sh := db.shards[m.TopicID]
|
|
if sh == nil {
|
|
s.mu.RUnlock()
|
|
return ErrShardNotFound
|
|
}
|
|
s.mu.RUnlock()
|
|
|
|
// TODO: enable some way to specify if the data should be overwritten
|
|
overwrite := true
|
|
|
|
// Write to shard.
|
|
return sh.writeSeries(overwrite, m.Data)
|
|
}
|
|
|
|
func (s *Server) createSeriesIfNotExists(database, name string, tags map[string]string) (uint32, error) {
|
|
// Try to find series locally first.
|
|
s.mu.RLock()
|
|
idx := s.databases[database]
|
|
if _, series := idx.MeasurementAndSeries(name, tags); series != nil {
|
|
s.mu.RUnlock()
|
|
return series.ID, nil
|
|
}
|
|
// release the read lock so the broadcast can actually go through and acquire the write lock
|
|
s.mu.RUnlock()
|
|
|
|
// If it doesn't exist then create a message and broadcast.
|
|
c := &createSeriesIfNotExistsCommand{Database: database, Name: name, Tags: tags}
|
|
_, err := s.broadcast(createSeriesIfNotExistsMessageType, c)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Lookup series again.
|
|
_, series := idx.MeasurementAndSeries(name, tags)
|
|
if series == nil {
|
|
return 0, ErrSeriesNotFound
|
|
}
|
|
return series.ID, nil
|
|
}
|
|
|
|
func (s *Server) MeasurementNames(database string) []string {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
db := s.databases[database]
|
|
if db == nil {
|
|
return nil
|
|
}
|
|
|
|
return db.names
|
|
}
|
|
|
|
func (s *Server) MeasurementSeriesIDs(database, measurement string) SeriesIDs {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
db := s.databases[database]
|
|
if db == nil {
|
|
return nil
|
|
}
|
|
|
|
return db.SeriesIDs([]string{measurement}, nil)
|
|
}
|
|
|
|
// processor runs in a separate goroutine and processes all incoming broker messages.
|
|
func (s *Server) processor(client MessagingClient, done chan struct{}) {
|
|
for {
|
|
// Read incoming message.
|
|
var m *messaging.Message
|
|
select {
|
|
case <-done:
|
|
return
|
|
case m = <-client.C():
|
|
}
|
|
|
|
// Process message.
|
|
var err error
|
|
switch m.Type {
|
|
case writeSeriesMessageType:
|
|
err = s.applyWriteSeries(m)
|
|
case createDataNodeMessageType:
|
|
err = s.applyCreateDataNode(m)
|
|
case deleteDataNodeMessageType:
|
|
err = s.applyDeleteDataNode(m)
|
|
case createDatabaseMessageType:
|
|
err = s.applyCreateDatabase(m)
|
|
case deleteDatabaseMessageType:
|
|
err = s.applyDeleteDatabase(m)
|
|
case createUserMessageType:
|
|
err = s.applyCreateUser(m)
|
|
case updateUserMessageType:
|
|
err = s.applyUpdateUser(m)
|
|
case deleteUserMessageType:
|
|
err = s.applyDeleteUser(m)
|
|
case createRetentionPolicyMessageType:
|
|
err = s.applyCreateRetentionPolicy(m)
|
|
case updateRetentionPolicyMessageType:
|
|
err = s.applyUpdateRetentionPolicy(m)
|
|
case deleteRetentionPolicyMessageType:
|
|
err = s.applyDeleteRetentionPolicy(m)
|
|
case createShardIfNotExistsMessageType:
|
|
err = s.applyCreateShardIfNotExists(m)
|
|
case setDefaultRetentionPolicyMessageType:
|
|
err = s.applySetDefaultRetentionPolicy(m)
|
|
case createSeriesIfNotExistsMessageType:
|
|
err = s.applyCreateSeriesIfNotExists(m)
|
|
}
|
|
|
|
// Sync high water mark and errors.
|
|
s.mu.Lock()
|
|
s.index = m.Index
|
|
if err != nil {
|
|
s.errors[m.Index] = err
|
|
}
|
|
s.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
// MessagingClient represents the client used to receive messages from brokers.
|
|
type MessagingClient interface {
|
|
// Publishes a message to the broker.
|
|
Publish(m *messaging.Message) (index uint64, err error)
|
|
|
|
// The streaming channel for all subscribed messages.
|
|
C() <-chan *messaging.Message
|
|
}
|
|
|
|
// DataNode represents a data node in the cluster.
|
|
type DataNode struct {
|
|
ID uint64
|
|
URL *url.URL
|
|
}
|
|
|
|
// newDataNode returns an instance of DataNode.
|
|
func newDataNode() *DataNode { return &DataNode{} }
|
|
|
|
type dataNodes []*DataNode
|
|
|
|
func (p dataNodes) Len() int { return len(p) }
|
|
func (p dataNodes) Less(i, j int) bool { return p[i].ID < p[j].ID }
|
|
func (p dataNodes) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
|
|
// BcryptCost is the cost associated with generating password with Bcrypt.
|
|
// This setting is lowered during testing to improve test suite performance.
|
|
var BcryptCost = 10
|
|
|
|
// User represents a user account on the system.
|
|
// It can be given read/write permissions to individual databases.
|
|
type User struct {
|
|
Name string `json:"name"`
|
|
Hash string `json:"hash"`
|
|
Admin bool `json:"admin,omitempty"`
|
|
}
|
|
|
|
// Authenticate returns nil if the password matches the user's password.
|
|
// Returns an error if the password was incorrect.
|
|
func (u *User) Authenticate(password string) error {
|
|
return bcrypt.CompareHashAndPassword([]byte(u.Hash), []byte(password))
|
|
}
|
|
|
|
// users represents a list of users, sortable by name.
|
|
type users []*User
|
|
|
|
func (p users) Len() int { return len(p) }
|
|
func (p users) Less(i, j int) bool { return p[i].Name < p[j].Name }
|
|
func (p users) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
|
|
type Matcher struct {
|
|
IsRegex bool
|
|
Name string
|
|
}
|
|
|
|
func (m *Matcher) Matches(name string) bool {
|
|
if m.IsRegex {
|
|
matches, _ := regexp.MatchString(m.Name, name)
|
|
return matches
|
|
}
|
|
return m.Name == name
|
|
}
|
|
|
|
// HashPassword generates a cryptographically secure hash for password.
|
|
// Returns an error if the password is invalid or a hash cannot be generated.
|
|
func HashPassword(password string) ([]byte, error) {
|
|
// The second arg is the cost of the hashing, higher is slower but makes
|
|
// it harder to brute force, since it will be really slow and impractical
|
|
return bcrypt.GenerateFromPassword([]byte(password), BcryptCost)
|
|
}
|
|
|
|
// ContinuousQuery represents a query that exists on the server and processes
|
|
// each incoming event.
|
|
type ContinuousQuery struct {
|
|
ID uint32
|
|
Query string
|
|
// TODO: ParsedQuery *parser.SelectQuery
|
|
}
|