882 lines
22 KiB
Go
882 lines
22 KiB
Go
package influxdb
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/boltdb/bolt"
|
|
"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"
|
|
|
|
// DefaultShardSpaceName is the name of a databases's default shard space.
|
|
DefaultShardSpaceName = "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 (
|
|
// broadcast messages
|
|
createDatabaseMessageType = messaging.MessageType(0x00)
|
|
deleteDatabaseMessageType = messaging.MessageType(0x01)
|
|
createShardSpaceMessageType = messaging.MessageType(0x02)
|
|
deleteShardSpaceMessageType = messaging.MessageType(0x03)
|
|
createClusterAdminMessageType = messaging.MessageType(0x04)
|
|
deleteClusterAdminMessageType = messaging.MessageType(0x05)
|
|
clusterAdminSetPasswordMessageType = messaging.MessageType(0x06)
|
|
createDBUserMessageType = messaging.MessageType(0x07)
|
|
deleteDBUserMessageType = messaging.MessageType(0x08)
|
|
dbUserSetPasswordMessageType = messaging.MessageType(0x09)
|
|
createShardIfNotExistsMessageType = messaging.MessageType(0x0a)
|
|
|
|
// per-topic messages
|
|
writeSeriesMessageType = messaging.MessageType(0x80)
|
|
)
|
|
|
|
// Server represents a collection of metadata and raw metric data.
|
|
type Server struct {
|
|
mu sync.RWMutex
|
|
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
|
|
|
|
databases map[string]*Database // databases by name
|
|
admins map[string]*ClusterAdmin // admins by name
|
|
}
|
|
|
|
// NewServer returns a new instance of Server.
|
|
// The server requires a client to the messaging broker to be passed in.
|
|
func NewServer(client MessagingClient) *Server {
|
|
assert(client != nil, "messaging client required")
|
|
return &Server{
|
|
client: client,
|
|
meta: &metastore{},
|
|
databases: make(map[string]*Database),
|
|
admins: make(map[string]*ClusterAdmin),
|
|
errors: make(map[uint64]error),
|
|
}
|
|
}
|
|
|
|
// Path returns the path used when opening the server.
|
|
// Returns an empty string when the server is closed.
|
|
func (s *Server) Path() string { 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
|
|
|
|
// Start goroutine to read messages from the broker.
|
|
s.done = make(chan struct{}, 0)
|
|
go s.processor(s.done)
|
|
|
|
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 notification.
|
|
close(s.done)
|
|
s.done = 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 {
|
|
// Load databases.
|
|
s.databases = make(map[string]*Database)
|
|
for _, db := range tx.databases() {
|
|
db.server = s
|
|
s.databases[db.name] = db
|
|
}
|
|
|
|
// Load cluster admins.
|
|
s.admins = make(map[string]*ClusterAdmin)
|
|
for _, u := range tx.clusterAdmins() {
|
|
s.admins[u.Name] = u
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
// Database creates a new database.
|
|
func (s *Server) Database(name string) *Database {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.databases[name]
|
|
}
|
|
|
|
// Databases returns a list of all databases, sorted by name.
|
|
func (s *Server) Databases() []*Database {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
var a databases
|
|
for _, db := range s.databases {
|
|
a = append(a, db)
|
|
}
|
|
sort.Sort(a)
|
|
return a
|
|
}
|
|
|
|
// 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) 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(s)
|
|
db.name = c.Name
|
|
|
|
// Persist to metastore.
|
|
s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveDatabase(db)
|
|
})
|
|
|
|
// Add to databases on server.
|
|
s.databases[c.Name] = db
|
|
|
|
return nil
|
|
}
|
|
|
|
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) 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.
|
|
s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.deleteDatabase(c.Name)
|
|
})
|
|
|
|
// Delete the database entry.
|
|
delete(s.databases, c.Name)
|
|
return nil
|
|
}
|
|
|
|
type deleteDatabaseCommand struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func (s *Server) applyCreateShardIfNotExists(m *messaging.Message) error {
|
|
var c createShardIfNotExistsSpaceCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
db := s.databases[c.Database]
|
|
if s.databases[c.Database] == nil {
|
|
return ErrDatabaseNotFound
|
|
}
|
|
|
|
// Check if a matching shard already exists.
|
|
if err, ok := db.applyCreateShardIfNotExists(m.Index, c.Space, c.Timestamp); err != nil {
|
|
return err
|
|
} else if !ok {
|
|
return nil
|
|
}
|
|
|
|
// Persist to metastore if a shard was created.
|
|
s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveDatabase(db)
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
type createShardIfNotExistsSpaceCommand struct {
|
|
Database string `json:"name"`
|
|
Space string `json:"space"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// ClusterAdmin returns an admin by name.
|
|
// Returns nil if the admin does not exist.
|
|
func (s *Server) ClusterAdmin(name string) *ClusterAdmin {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.admins[name]
|
|
}
|
|
|
|
// ClusterAdmins returns a list of all cluster admins, sorted by name.
|
|
func (s *Server) ClusterAdmins() []*ClusterAdmin {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
var a clusterAdmins
|
|
for _, u := range s.admins {
|
|
a = append(a, u)
|
|
}
|
|
sort.Sort(a)
|
|
return a
|
|
}
|
|
|
|
// CreateClusterAdmin creates a cluster admin on the server.
|
|
func (s *Server) CreateClusterAdmin(username, password string) error {
|
|
c := &createClusterAdminCommand{Username: username, Password: password}
|
|
_, err := s.broadcast(createClusterAdminMessageType, c)
|
|
return err
|
|
}
|
|
|
|
func (s *Server) applyCreateClusterAdmin(m *messaging.Message) error {
|
|
var c createClusterAdminCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Validate admin.
|
|
if c.Username == "" {
|
|
return ErrUsernameRequired
|
|
} else if s.admins[c.Username] != nil {
|
|
return ErrClusterAdminExists
|
|
}
|
|
|
|
// Generate the hash of the password.
|
|
hash, err := HashPassword(c.Password)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create the cluster admin.
|
|
u := &ClusterAdmin{
|
|
CommonUser: CommonUser{
|
|
Name: c.Username,
|
|
Hash: string(hash),
|
|
},
|
|
}
|
|
|
|
// Persist to metastore.
|
|
s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveClusterAdmin(u)
|
|
})
|
|
|
|
s.admins[u.Name] = u
|
|
return nil
|
|
}
|
|
|
|
type createClusterAdminCommand struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
// DeleteClusterAdmin removes a cluster admin from the server.
|
|
func (s *Server) DeleteClusterAdmin(username string) error {
|
|
c := &deleteClusterAdminCommand{Username: username}
|
|
_, err := s.broadcast(deleteClusterAdminMessageType, c)
|
|
return err
|
|
}
|
|
|
|
func (s *Server) applyDeleteClusterAdmin(m *messaging.Message) error {
|
|
var c deleteClusterAdminCommand
|
|
mustUnmarshalJSON(m.Data, &c)
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Validate admin.
|
|
if c.Username == "" {
|
|
return ErrUsernameRequired
|
|
} else if s.admins[c.Username] == nil {
|
|
return ErrClusterAdminNotFound
|
|
}
|
|
|
|
// Remove from metastore.
|
|
s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.deleteClusterAdmin(c.Username)
|
|
})
|
|
|
|
// Delete the cluster admin.
|
|
delete(s.admins, c.Username)
|
|
return nil
|
|
}
|
|
|
|
type deleteClusterAdminCommand struct {
|
|
Username string `json:"username"`
|
|
}
|
|
|
|
func (s *Server) applyDBUserSetPassword(m *messaging.Message) error {
|
|
var c dbUserSetPasswordCommand
|
|
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
|
|
}
|
|
|
|
// Update password change in database.
|
|
if err := db.applyChangePassword(c.Username, c.Password); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Persist to metastore.
|
|
s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveDatabase(db)
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
type dbUserSetPasswordCommand struct {
|
|
Database string `json:"database"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
func (s *Server) applyCreateDBUser(m *messaging.Message) error {
|
|
var c createDBUserCommand
|
|
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
|
|
}
|
|
|
|
if err := db.applyCreateUser(c.Username, c.Password, c.Permissions); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Persist to metastore.
|
|
s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveDatabase(db)
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
type createDBUserCommand struct {
|
|
Database string `json:"database"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
Permissions []string `json:"permissions"`
|
|
}
|
|
|
|
func (s *Server) applyDeleteDBUser(m *messaging.Message) error {
|
|
var c deleteDBUserCommand
|
|
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
|
|
}
|
|
|
|
// Remove user from database.
|
|
if err := db.applyDeleteUser(c.Username); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Persist to metastore.
|
|
s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveDatabase(db)
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
type deleteDBUserCommand struct {
|
|
Database string `json:"database"`
|
|
Username string `json:"username"`
|
|
}
|
|
|
|
func (s *Server) applyCreateShardSpace(m *messaging.Message) error {
|
|
var c createShardSpaceCommand
|
|
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
|
|
}
|
|
|
|
if err := db.applyCreateShardSpace(c.Name, c.Regex, c.Retention, c.Duration, c.ReplicaN, c.SplitN); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Persist to metastore.
|
|
s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveDatabase(db)
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
type createShardSpaceCommand struct {
|
|
Database string `json:"database"`
|
|
Name string `json:"name"`
|
|
Regex string `json:"regex"`
|
|
Retention time.Duration `json:"retention"`
|
|
Duration time.Duration `json:"duration"`
|
|
ReplicaN uint32 `json:"replicaN"`
|
|
SplitN uint32 `json:"splitN"`
|
|
}
|
|
|
|
func (s *Server) applyDeleteShardSpace(m *messaging.Message) error {
|
|
var c deleteShardSpaceCommand
|
|
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
|
|
}
|
|
|
|
// Remove shard space from database.
|
|
if err := db.applyDeleteShardSpace(c.Name); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Persist to metastore.
|
|
s.meta.mustUpdate(func(tx *metatx) error {
|
|
return tx.saveDatabase(db)
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
type deleteShardSpaceCommand struct {
|
|
Database string `json:"database"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
/* TEMPORARILY REMOVED FOR PROTOBUFS.
|
|
func (s *Server) applyWriteSeries(m *messaging.Message) error {
|
|
req := &protocol.WriteSeriesRequest{}
|
|
if err := proto.Unmarshal(m.Data, req); err != nil {
|
|
panic("unmarshal request: " + err.Error())
|
|
}
|
|
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Retrieve the database.
|
|
db := s.databases[req.GetDatabase()]
|
|
if db == nil {
|
|
return ErrDatabaseNotFound
|
|
}
|
|
|
|
if err := db.applyWriteSeries(req.GetSeries()); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
*/
|
|
|
|
// processor runs in a separate goroutine and processes all incoming broker messages.
|
|
func (s *Server) processor(done chan struct{}) {
|
|
client := s.client
|
|
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 createDatabaseMessageType:
|
|
err = s.applyCreateDatabase(m)
|
|
case deleteDatabaseMessageType:
|
|
err = s.applyDeleteDatabase(m)
|
|
case createClusterAdminMessageType:
|
|
err = s.applyCreateClusterAdmin(m)
|
|
case deleteClusterAdminMessageType:
|
|
err = s.applyDeleteClusterAdmin(m)
|
|
case createDBUserMessageType:
|
|
err = s.applyCreateDBUser(m)
|
|
case deleteDBUserMessageType:
|
|
err = s.applyDeleteDBUser(m)
|
|
case dbUserSetPasswordMessageType:
|
|
err = s.applyDBUserSetPassword(m)
|
|
case createShardSpaceMessageType:
|
|
err = s.applyCreateShardSpace(m)
|
|
case deleteShardSpaceMessageType:
|
|
err = s.applyDeleteShardSpace(m)
|
|
case createShardIfNotExistsMessageType:
|
|
err = s.applyCreateShardIfNotExists(m)
|
|
case writeSeriesMessageType:
|
|
/* TEMPORARILY REMOVED FOR PROTOBUFS.
|
|
err = s.applyWriteSeries(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
|
|
}
|
|
|
|
// metastore represents the low-level data store for metadata.
|
|
type metastore struct {
|
|
db *bolt.DB
|
|
}
|
|
|
|
// open initializes the metastore.
|
|
func (m *metastore) open(path string) error {
|
|
// Open the bolt-backed database.
|
|
db, err := bolt.Open(path, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m.db = db
|
|
|
|
// Initialize the metastore.
|
|
if err := m.init(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// close closes the store.
|
|
func (m *metastore) close() error {
|
|
if m.db != nil {
|
|
return m.db.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// init initializes the metastore to ensure all top-level buckets are created.
|
|
func (m *metastore) init() error {
|
|
return m.db.Update(func(tx *bolt.Tx) error {
|
|
_, _ = tx.CreateBucketIfNotExists([]byte("Databases"))
|
|
_, _ = tx.CreateBucketIfNotExists([]byte("Series"))
|
|
_, _ = tx.CreateBucketIfNotExists([]byte("ClusterAdmins"))
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// view executes a function in the context of a read-only transaction.
|
|
func (m *metastore) view(fn func(*metatx) error) error {
|
|
return m.db.View(func(tx *bolt.Tx) error { return fn(&metatx{tx}) })
|
|
}
|
|
|
|
// update executes a function in the context of a read-write transaction.
|
|
func (m *metastore) update(fn func(*metatx) error) error {
|
|
return m.db.Update(func(tx *bolt.Tx) error { return fn(&metatx{tx}) })
|
|
}
|
|
|
|
// mustUpdate executes a function in the context of a read-write transaction.
|
|
// Panics if update returns an error.
|
|
func (m *metastore) mustUpdate(fn func(*metatx) error) {
|
|
if err := m.update(fn); err != nil {
|
|
panic("metastore update: " + err.Error())
|
|
}
|
|
}
|
|
|
|
// metatx represents a metastore transaction.
|
|
type metatx struct {
|
|
*bolt.Tx
|
|
}
|
|
|
|
// database returns a database from the metastore by name.
|
|
func (tx *metatx) database(name string) (db *Database) {
|
|
if v := tx.Bucket([]byte("Databases")).Get([]byte(name)); v != nil {
|
|
mustUnmarshalJSON(v, &db)
|
|
}
|
|
return
|
|
}
|
|
|
|
// databases returns a list of all databases from the metastore.
|
|
func (tx *metatx) databases() (a []*Database) {
|
|
c := tx.Bucket([]byte("Databases")).Cursor()
|
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
|
db := newDatabase(nil)
|
|
mustUnmarshalJSON(v, &db)
|
|
a = append(a, db)
|
|
}
|
|
return
|
|
}
|
|
|
|
// saveDatabase persists a database to the metastore.
|
|
func (tx *metatx) saveDatabase(db *Database) error {
|
|
return tx.Bucket([]byte("Databases")).Put([]byte(db.name), mustMarshalJSON(db))
|
|
}
|
|
|
|
// deleteDatabase removes database from the metastore.
|
|
func (tx *metatx) deleteDatabase(name string) error {
|
|
return tx.Bucket([]byte("Databases")).Delete([]byte(name))
|
|
}
|
|
|
|
// series returns a series by database and name.
|
|
func (tx *metatx) series(database, name string) (s *Series) {
|
|
b := tx.Bucket([]byte("Series")).Bucket([]byte(database))
|
|
if b == nil {
|
|
return nil
|
|
}
|
|
if v := b.Get([]byte(name)); v != nil {
|
|
mustUnmarshalJSON(v, &s)
|
|
}
|
|
return
|
|
}
|
|
|
|
// saveSeries persists a series to the metastore.
|
|
func (tx *metatx) saveSeries(database string, s *Series) error {
|
|
b, err := tx.Bucket([]byte("Series")).CreateBucketIfNotExists([]byte(database))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return b.Put([]byte(s.Name), mustMarshalJSON(s))
|
|
}
|
|
|
|
// deleteSeries removes a series from the metastore.
|
|
func (tx *metatx) deleteSeries(database, name string) error {
|
|
b := tx.Bucket([]byte("Series")).Bucket([]byte(database))
|
|
if b == nil {
|
|
return nil
|
|
}
|
|
return b.Delete([]byte(name))
|
|
}
|
|
|
|
// clusterAdmin returns a cluster admin from the metastore by name.
|
|
func (tx *metatx) clusterAdmin(name string) (u *ClusterAdmin) {
|
|
if v := tx.Bucket([]byte("ClusterAdmins")).Get([]byte(name)); v != nil {
|
|
mustUnmarshalJSON(v, &u)
|
|
}
|
|
return
|
|
}
|
|
|
|
// clusterAdmins returns a list of all cluster admins from the metastore.
|
|
func (tx *metatx) clusterAdmins() (a []*ClusterAdmin) {
|
|
c := tx.Bucket([]byte("ClusterAdmins")).Cursor()
|
|
for k, v := c.First(); k != nil; k, v = c.Next() {
|
|
u := &ClusterAdmin{}
|
|
mustUnmarshalJSON(v, &u)
|
|
a = append(a, u)
|
|
}
|
|
return
|
|
}
|
|
|
|
// saveClusterAdmin persists a cluster admin to the metastore.
|
|
func (tx *metatx) saveClusterAdmin(u *ClusterAdmin) error {
|
|
return tx.Bucket([]byte("ClusterAdmins")).Put([]byte(u.Name), mustMarshalJSON(u))
|
|
}
|
|
|
|
// deleteClusterAdmin removes the cluster admin from the metastore.
|
|
func (tx *metatx) deleteClusterAdmin(name string) error {
|
|
return tx.Bucket([]byte("ClusterAdmins")).Delete([]byte(name))
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// mustMarshal encodes a value to JSON.
|
|
// This will panic if an error occurs. This should only be used internally when
|
|
// an invalid marshal will cause corruption and a panic is appropriate.
|
|
func mustMarshalJSON(v interface{}) []byte {
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
panic("marshal: " + err.Error())
|
|
}
|
|
return b
|
|
}
|
|
|
|
// mustUnmarshalJSON decodes a value from JSON.
|
|
// This will panic if an error occurs. This should only be used internally when
|
|
// an invalid unmarshal will cause corruption and a panic is appropriate.
|
|
func mustUnmarshalJSON(b []byte, v interface{}) {
|
|
if err := json.Unmarshal(b, v); err != nil {
|
|
panic("unmarshal: " + err.Error())
|
|
}
|
|
}
|
|
|
|
// assert will panic with a given formatted message if the given condition is false.
|
|
func assert(condition bool, msg string, v ...interface{}) {
|
|
if !condition {
|
|
panic(fmt.Sprintf("assert failed: "+msg, v...))
|
|
}
|
|
}
|
|
|
|
func warn(v ...interface{}) { fmt.Fprintln(os.Stderr, v...) }
|
|
func warnf(msg string, v ...interface{}) { fmt.Fprintf(os.Stderr, msg+"\n", v...) }
|