Merge pull request #1141 from influxdb/refactor_shard_space

Refactor ShardSpace to RetentionPolicy
pull/1143/head
Paul Dix 2014-11-18 23:27:06 -05:00
commit 00fce7e56f
5 changed files with 327 additions and 257 deletions

View File

@ -3,7 +3,6 @@ package influxdb
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"regexp"
"sort" "sort"
"sync" "sync"
"time" "time"
@ -12,17 +11,18 @@ import (
// "github.com/influxdb/influxdb/messaging" // "github.com/influxdb/influxdb/messaging"
) )
// Database represents a collection of shard spaces. // Database represents a collection of retention policies.
type Database struct { type Database struct {
mu sync.RWMutex mu sync.RWMutex
server *Server server *Server
name string name string
users map[string]*DBUser // database users by name users map[string]*DBUser // database users by name
spaces map[string]*ShardSpace // shard spaces by name policies map[string]*RetentionPolicy // retention policies by name
shards map[uint64]*Shard // shards by id shards map[uint64]*Shard // shards by id
series map[string]*Series // series by name series map[string]*Series // series by name
defaultRetentionPolicy string
maxFieldID uint64 // largest field id in use maxFieldID uint64 // largest field id in use
} }
@ -31,7 +31,7 @@ func newDatabase(s *Server) *Database {
return &Database{ return &Database{
server: s, server: s,
users: make(map[string]*DBUser), users: make(map[string]*DBUser),
spaces: make(map[string]*ShardSpace), policies: make(map[string]*RetentionPolicy),
shards: make(map[uint64]*Shard), shards: make(map[uint64]*Shard),
series: make(map[string]*Series), series: make(map[string]*Series),
} }
@ -44,6 +44,13 @@ func (db *Database) Name() string {
return db.name return db.name
} }
// DefaultRetentionPolicy returns the retention policy that writes and queries will default to or nil if not set.
func (db *Database) DefaultRetentionPolicy() *RetentionPolicy {
db.mu.Lock()
defer db.mu.Unlock()
return db.policies[db.defaultRetentionPolicy]
}
// User returns a database user by name. // User returns a database user by name.
func (db *Database) User(name string) *DBUser { func (db *Database) User(name string) *DBUser {
db.mu.Lock() db.mu.Lock()
@ -180,59 +187,40 @@ func (db *Database) applyChangePassword(username, newPassword string) error {
return nil return nil
} }
// ShardSpace returns a shard space by name. // RetentionPolicy returns a retention policy by name.
func (db *Database) ShardSpace(name string) *ShardSpace { func (db *Database) RetentionPolicy(name string) *RetentionPolicy {
db.mu.Lock() db.mu.Lock()
defer db.mu.Unlock() defer db.mu.Unlock()
return db.spaces[name] return db.policies[name]
} }
// shardSpaceBySeries returns a shard space that matches a series name. // CreateRetentionPolicy creates a retention policy in the database.
func (db *Database) shardSpaceBySeries(name string) *ShardSpace { func (db *Database) CreateRetentionPolicy(ss *RetentionPolicy) error {
for _, ss := range db.spaces { c := &createRetentionPolicyCommand{
if ss.Regex.MatchString(name) {
return ss
}
}
return nil
}
// CreateShardSpace creates a shard space in the database.
func (db *Database) CreateShardSpace(ss *ShardSpace) error {
c := &createShardSpaceCommand{
Database: db.Name(), Database: db.Name(),
Name: ss.Name, Name: ss.Name,
Retention: ss.Retention,
Duration: ss.Duration, Duration: ss.Duration,
ReplicaN: ss.ReplicaN, ReplicaN: ss.ReplicaN,
SplitN: ss.SplitN, SplitN: ss.SplitN,
} }
if ss.Regex != nil { _, err := db.server.broadcast(createRetentionPolicyMessageType, c)
c.Regex = ss.Regex.String()
}
_, err := db.server.broadcast(createShardSpaceMessageType, c)
return err return err
} }
func (db *Database) applyCreateShardSpace(name, regex string, retention, duration time.Duration, replicaN, splitN uint32) error { func (db *Database) applyCreateRetentionPolicy(name string, duration time.Duration, replicaN, splitN uint32) error {
db.mu.Lock() db.mu.Lock()
defer db.mu.Unlock() defer db.mu.Unlock()
// Validate shard space. // Validate retention policy.
if name == "" { if name == "" {
return ErrShardSpaceNameRequired return ErrRetentionPolicyNameRequired
} else if db.spaces[name] != nil { } else if db.policies[name] != nil {
return ErrShardSpaceExists return ErrRetentionPolicyExists
} }
// Compile regex.
re := regexp.MustCompile(regex)
// Add space to the database. // Add space to the database.
db.spaces[name] = &ShardSpace{ db.policies[name] = &RetentionPolicy{
Name: name, Name: name,
Regex: re,
Retention: retention,
Duration: duration, Duration: duration,
ReplicaN: replicaN, ReplicaN: replicaN,
SplitN: splitN, SplitN: splitN,
@ -241,32 +229,52 @@ func (db *Database) applyCreateShardSpace(name, regex string, retention, duratio
return nil return nil
} }
// DeleteShardSpace removes a shard space from the database. // DeleteRetentionPolicy removes a retention policy from the database.
func (db *Database) DeleteShardSpace(name string) error { func (db *Database) DeleteRetentionPolicy(name string) error {
c := &deleteShardSpaceCommand{Database: db.Name(), Name: name} c := &deleteRetentionPolicyCommand{Database: db.Name(), Name: name}
_, err := db.server.broadcast(deleteShardSpaceMessageType, c) _, err := db.server.broadcast(deleteRetentionPolicyMessageType, c)
return err return err
} }
func (db *Database) applyDeleteShardSpace(name string) error { func (db *Database) applyDeleteRetentionPolicy(name string) error {
db.mu.Lock() db.mu.Lock()
defer db.mu.Unlock() defer db.mu.Unlock()
// Validate shard space. // Validate retention policy.
if name == "" { if name == "" {
return ErrShardSpaceNameRequired return ErrRetentionPolicyNameRequired
} else if db.spaces[name] == nil { } else if db.policies[name] == nil {
return ErrShardSpaceNotFound return ErrRetentionPolicyNotFound
} }
// Remove shard space. // Remove retention policy.
delete(db.spaces, name) delete(db.policies, name)
return nil
}
// SetDefaultRetentionPolicy sets the default policy to write data into and query from on a database.
func (db *Database) SetDefaultRetentionPolicy(name string) error {
c := &setDefaultRetentionPolicyCommand{Database: db.Name(), Name: name}
_, err := db.server.broadcast(setDefaultRetentionPolicyMessageType, c)
return err
}
func (db *Database) applySetDefaultRetentionPolicy(name string) error {
db.mu.Lock()
defer db.mu.Unlock()
// Check the retention policy exists
if db.policies[name] == nil {
return ErrRetentionPolicyNotFound
}
db.defaultRetentionPolicy = name
return nil return nil
} }
// shard returns a shard by id. // shard returns a shard by id.
func (db *Database) shard(id uint64) *Shard { func (db *Database) shard(id uint64) *Shard {
for _, ss := range db.spaces { for _, ss := range db.policies {
for _, s := range ss.Shards { for _, s := range ss.Shards {
if s.ID == id { if s.ID == id {
return s return s
@ -276,21 +284,21 @@ func (db *Database) shard(id uint64) *Shard {
return nil return nil
} }
// CreateShardIfNotExists creates a shard for a shard space for a given timestamp. // CreateShardIfNotExists creates a shard for a retention policy for a given timestamp.
func (db *Database) CreateShardIfNotExists(space string, timestamp time.Time) error { func (db *Database) CreateShardIfNotExists(space string, timestamp time.Time) error {
c := &createShardIfNotExistsSpaceCommand{Database: db.name, Space: space, Timestamp: timestamp} c := &createShardIfNotExistsSpaceCommand{Database: db.name, Space: space, Timestamp: timestamp}
_, err := db.server.broadcast(createShardIfNotExistsMessageType, c) _, err := db.server.broadcast(createShardIfNotExistsMessageType, c)
return err return err
} }
func (db *Database) applyCreateShardIfNotExists(id uint64, space string, timestamp time.Time) (error, bool) { func (db *Database) applyCreateShardIfNotExists(id uint64, policy string, timestamp time.Time) (error, bool) {
db.mu.Lock() db.mu.Lock()
defer db.mu.Unlock() defer db.mu.Unlock()
// Validate shard space. // Validate retention policy.
ss := db.spaces[space] ss := db.policies[policy]
if ss == nil { if ss == nil {
return ErrShardSpaceNotFound, false return ErrRetentionPolicyNotFound, false
} }
// If we can match to an existing shard date range then just ignore request. // If we can match to an existing shard date range then just ignore request.
@ -311,7 +319,7 @@ func (db *Database) applyCreateShardIfNotExists(id uint64, space string, timesta
panic("unable to open shard: " + err.Error()) panic("unable to open shard: " + err.Error())
} }
// Append to shard space. // Append to retention policy.
ss.Shards = append(ss.Shards, s) ss.Shards = append(ss.Shards, s)
return nil, true return nil, true
@ -322,15 +330,15 @@ func (db *Database) WriteSeries(name string, tags map[string]string, timestamp t
panic("not yet implemented: Database.WriteSeries()") panic("not yet implemented: Database.WriteSeries()")
/* TEMPORARILY REMOVED FOR PROTOBUFS. /* TEMPORARILY REMOVED FOR PROTOBUFS.
// Find shard space matching the series and split points by shard. // Find retention policy matching the series and split points by shard.
db.mu.Lock() db.mu.Lock()
name := db.name name := db.name
space := db.shardSpaceBySeries(series.GetName()) space := db.retentionPolicyBySeries(series.GetName())
db.mu.Unlock() db.mu.Unlock()
// Ensure there is a space available. // Ensure there is a space available.
if space == nil { if space == nil {
return ErrShardSpaceNotFound return ErrRetentionPolicyNotFound
} }
// Group points by shard. // Group points by shard.
@ -458,12 +466,13 @@ func (db *Database) MarshalJSON() ([]byte, error) {
// Copy over properties to intermediate type. // Copy over properties to intermediate type.
var o databaseJSON var o databaseJSON
o.Name = db.name o.Name = db.name
o.DefaultRetentionPolicy = db.defaultRetentionPolicy
o.MaxFieldID = db.maxFieldID o.MaxFieldID = db.maxFieldID
for _, u := range db.users { for _, u := range db.users {
o.Users = append(o.Users, u) o.Users = append(o.Users, u)
} }
for _, ss := range db.spaces { for _, ss := range db.policies {
o.Spaces = append(o.Spaces, ss) o.Policies = append(o.Policies, ss)
} }
for _, s := range db.shards { for _, s := range db.shards {
o.Shards = append(o.Shards, s) o.Shards = append(o.Shards, s)
@ -484,6 +493,7 @@ func (db *Database) UnmarshalJSON(data []byte) error {
// Copy over properties from intermediate type. // Copy over properties from intermediate type.
db.name = o.Name db.name = o.Name
db.defaultRetentionPolicy = o.DefaultRetentionPolicy
db.maxFieldID = o.MaxFieldID db.maxFieldID = o.MaxFieldID
// Copy users. // Copy users.
@ -492,10 +502,10 @@ func (db *Database) UnmarshalJSON(data []byte) error {
db.users[u.Name] = u db.users[u.Name] = u
} }
// Copy shard spaces. // Copy shard policies.
db.spaces = make(map[string]*ShardSpace) db.policies = make(map[string]*RetentionPolicy)
for _, ss := range o.Spaces { for _, ss := range o.Policies {
db.spaces[ss.Name] = ss db.policies[ss.Name] = ss
} }
// Copy shards. // Copy shards.
@ -516,9 +526,10 @@ func (db *Database) UnmarshalJSON(data []byte) error {
// databaseJSON represents the JSON-serialization format for a database. // databaseJSON represents the JSON-serialization format for a database.
type databaseJSON struct { type databaseJSON struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
DefaultRetentionPolicy string `json:"defaultRetentionPolicy,omitempty"`
MaxFieldID uint64 `json:"maxFieldID,omitempty"` MaxFieldID uint64 `json:"maxFieldID,omitempty"`
Users []*DBUser `json:"users,omitempty"` Users []*DBUser `json:"users,omitempty"`
Spaces []*ShardSpace `json:"spaces,omitempty"` Policies []*RetentionPolicy `json:"policies,omitempty"`
Shards []*Shard `json:"shards,omitempty"` Shards []*Shard `json:"shards,omitempty"`
Series []*Series `json:"series,omitempty"` Series []*Series `json:"series,omitempty"`
} }
@ -530,15 +541,12 @@ func (p databases) Len() int { return len(p) }
func (p databases) Less(i, j int) bool { return p[i].name < p[j].name } func (p databases) Less(i, j int) bool { return p[i].name < p[j].name }
func (p databases) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p databases) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// ShardSpace represents a policy for creating new shards in a database. // RetentionPolicy represents a policy for creating new shards in a database and how long they're kept around for.
type ShardSpace struct { type RetentionPolicy struct {
// Unique name within database. Required. // Unique name within database. Required.
Name string Name string
// Expression used to match against series. Optional. Defaults to /.*/. // Length of time to keep data around
Regex *regexp.Regexp
Retention time.Duration
Duration time.Duration Duration time.Duration
ReplicaN uint32 ReplicaN uint32
@ -547,21 +555,19 @@ type ShardSpace struct {
Shards []*Shard Shards []*Shard
} }
// NewShardSpace returns a new instance of ShardSpace with defaults set. // NewRetentionPolicy returns a new instance of RetentionPolicy with defaults set.
func NewShardSpace() *ShardSpace { func NewRetentionPolicy() *RetentionPolicy {
return &ShardSpace{ return &RetentionPolicy{
Regex: regexp.MustCompile(`.*`),
ReplicaN: DefaultReplicaN, ReplicaN: DefaultReplicaN,
SplitN: DefaultSplitN, SplitN: DefaultSplitN,
Retention: DefaultShardRetention, Duration: DefaultShardRetention,
Duration: DefaultShardDuration,
} }
} }
/* /*
// SplitPoints groups a set of points by shard id. // SplitPoints groups a set of points by shard id.
// Also returns a list of timestamps that did not match an existing shard. // Also returns a list of timestamps that did not match an existing shard.
func (ss *ShardSpace) Split(a []*protocol.Point) (points map[uint64][]*protocol.Point, unassigned []*protocol.Point) { func (ss *RetentionPolicy) Split(a []*protocol.Point) (points map[uint64][]*protocol.Point, unassigned []*protocol.Point) {
points = make(map[uint64][]*protocol.Point) points = make(map[uint64][]*protocol.Point)
for _, p := range a { for _, p := range a {
if s := ss.ShardByTimestamp(time.Unix(0, p.GetTimestamp())); s != nil { if s := ss.ShardByTimestamp(time.Unix(0, p.GetTimestamp())); s != nil {
@ -576,7 +582,7 @@ func (ss *ShardSpace) Split(a []*protocol.Point) (points map[uint64][]*protocol.
// ShardByTimestamp returns the shard in the space that owns a given timestamp. // ShardByTimestamp returns the shard in the space that owns a given timestamp.
// Returns nil if the shard does not exist. // Returns nil if the shard does not exist.
func (ss *ShardSpace) ShardByTimestamp(timestamp time.Time) *Shard { func (ss *RetentionPolicy) ShardByTimestamp(timestamp time.Time) *Shard {
for _, s := range ss.Shards { for _, s := range ss.Shards {
if timeBetween(timestamp, s.StartTime, s.EndTime) { if timeBetween(timestamp, s.StartTime, s.EndTime) {
return s return s
@ -585,22 +591,20 @@ func (ss *ShardSpace) ShardByTimestamp(timestamp time.Time) *Shard {
return nil return nil
} }
// MarshalJSON encodes a shard space to a JSON-encoded byte slice. // MarshalJSON encodes a retention policy to a JSON-encoded byte slice.
func (s *ShardSpace) MarshalJSON() ([]byte, error) { func (s *RetentionPolicy) MarshalJSON() ([]byte, error) {
return json.Marshal(&shardSpaceJSON{ return json.Marshal(&retentionPolicyJSON{
Name: s.Name, Name: s.Name,
Regex: s.Regex.String(),
Retention: s.Retention,
Duration: s.Duration, Duration: s.Duration,
ReplicaN: s.ReplicaN, ReplicaN: s.ReplicaN,
SplitN: s.SplitN, SplitN: s.SplitN,
}) })
} }
// UnmarshalJSON decodes a JSON-encoded byte slice to a shard space. // UnmarshalJSON decodes a JSON-encoded byte slice to a retention policy.
func (s *ShardSpace) UnmarshalJSON(data []byte) error { func (s *RetentionPolicy) UnmarshalJSON(data []byte) error {
// Decode into intermediate type. // Decode into intermediate type.
var o shardSpaceJSON var o retentionPolicyJSON
if err := json.Unmarshal(data, &o); err != nil { if err := json.Unmarshal(data, &o); err != nil {
return err return err
} }
@ -609,34 +613,26 @@ func (s *ShardSpace) UnmarshalJSON(data []byte) error {
s.Name = o.Name s.Name = o.Name
s.ReplicaN = o.ReplicaN s.ReplicaN = o.ReplicaN
s.SplitN = o.SplitN s.SplitN = o.SplitN
s.Retention = o.Retention
s.Duration = o.Duration s.Duration = o.Duration
s.Shards = o.Shards s.Shards = o.Shards
s.Regex, _ = regexp.Compile(o.Regex)
if s.Regex == nil {
s.Regex = regexp.MustCompile(`.*`)
}
return nil return nil
} }
// shardSpaceJSON represents an intermediate struct for JSON marshaling. // retentionPolicyJSON represents an intermediate struct for JSON marshaling.
type shardSpaceJSON struct { type retentionPolicyJSON struct {
Name string `json:"name"` Name string `json:"name"`
Regex string `json:"regex,omitempty"`
ReplicaN uint32 `json:"replicaN,omitempty"` ReplicaN uint32 `json:"replicaN,omitempty"`
SplitN uint32 `json:"splitN,omitempty"` SplitN uint32 `json:"splitN,omitempty"`
Retention time.Duration `json:"retention,omitempty"`
Duration time.Duration `json:"duration,omitempty"` Duration time.Duration `json:"duration,omitempty"`
Shards []*Shard `json:"shards,omitempty"` Shards []*Shard `json:"shards,omitempty"`
} }
// ShardSpaces represents a list of shard spaces. // RetentionPolicys represents a list of shard policies.
type ShardSpaces []*ShardSpace type RetentionPolicys []*RetentionPolicy
// Shards returns a list of all shards for all spaces. // Shards returns a list of all shards for all policies.
func (a ShardSpaces) Shards() []*Shard { func (a RetentionPolicys) Shards() []*Shard {
var shards []*Shard var shards []*Shard
for _, ss := range a { for _, ss := range a {
shards = append(shards, ss.Shards...) shards = append(shards, ss.Shards...)

View File

@ -2,7 +2,6 @@ package influxdb_test
import ( import (
"reflect" "reflect"
"regexp"
"testing" "testing"
"time" "time"
@ -216,8 +215,8 @@ func TestDatabase_Users(t *testing.T) {
} }
} }
// Ensure the database can create a new shard space. // Ensure the database can create a new retention policy.
func TestDatabase_CreateShardSpace(t *testing.T) { func TestDatabase_CreateRetentionPolicy(t *testing.T) {
s := OpenServer(NewMessagingClient()) s := OpenServer(NewMessagingClient())
defer s.Close() defer s.Close()
@ -226,30 +225,28 @@ func TestDatabase_CreateShardSpace(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// Create a shard space on the database. // Create a retention policy on the database.
ss := &influxdb.ShardSpace{ rp := &influxdb.RetentionPolicy{
Name: "bar", Name: "bar",
Regex: regexp.MustCompile(`myseries`),
Duration: time.Hour, Duration: time.Hour,
Retention: time.Minute,
ReplicaN: 2, ReplicaN: 2,
SplitN: 3, SplitN: 3,
} }
if err := s.Database("foo").CreateShardSpace(ss); err != nil { if err := s.Database("foo").CreateRetentionPolicy(rp); err != nil {
t.Fatal(err) t.Fatal(err)
} }
s.Restart() s.Restart()
// Verify that the user exists. // Verify that the policy exists.
if o := s.Database("foo").ShardSpace("bar"); o == nil { if o := s.Database("foo").RetentionPolicy("bar"); o == nil {
t.Fatalf("shard space not found") t.Fatalf("retention policy not found")
} else if !reflect.DeepEqual(ss, o) { } else if !reflect.DeepEqual(rp, o) {
t.Fatalf("shard space mismatch: %#v", o) t.Fatalf("retention policy mismatch: %#v", o)
} }
} }
// Ensure the server returns an error when creating a shard space after db is dropped. // Ensure the server returns an error when creating a retention policy after db is dropped.
func TestDatabase_CreateShardSpace_ErrDatabaseNotFound(t *testing.T) { func TestDatabase_CreateRetentionPolicy_ErrDatabaseNotFound(t *testing.T) {
s := OpenServer(NewMessagingClient()) s := OpenServer(NewMessagingClient())
defer s.Close() defer s.Close()
@ -258,62 +255,62 @@ func TestDatabase_CreateShardSpace_ErrDatabaseNotFound(t *testing.T) {
db := s.Database("foo") db := s.Database("foo")
s.DeleteDatabase("foo") s.DeleteDatabase("foo")
// Create a shard space on the database. // Create a retention policy on the database.
if err := db.CreateShardSpace(&influxdb.ShardSpace{Name: "bar"}); err != influxdb.ErrDatabaseNotFound { if err := db.CreateRetentionPolicy(&influxdb.RetentionPolicy{Name: "bar"}); err != influxdb.ErrDatabaseNotFound {
t.Fatal(err) t.Fatal(err)
} }
} }
// Ensure the server returns an error when creating a shard space without a name. // Ensure the server returns an error when creating a retention policy without a name.
func TestDatabase_CreateShardSpace_ErrShardSpaceNameRequired(t *testing.T) { func TestDatabase_CreateRetentionPolicy_ErrRetentionPolicyNameRequired(t *testing.T) {
s := OpenServer(NewMessagingClient()) s := OpenServer(NewMessagingClient())
defer s.Close() defer s.Close()
s.CreateDatabase("foo") s.CreateDatabase("foo")
if err := s.Database("foo").CreateShardSpace(&influxdb.ShardSpace{Name: ""}); err != influxdb.ErrShardSpaceNameRequired { if err := s.Database("foo").CreateRetentionPolicy(&influxdb.RetentionPolicy{Name: ""}); err != influxdb.ErrRetentionPolicyNameRequired {
t.Fatal(err) t.Fatal(err)
} }
} }
// Ensure the server returns an error when creating a duplicate shard space. // Ensure the server returns an error when creating a duplicate retention policy.
func TestDatabase_CreateShardSpace_ErrShardSpaceExists(t *testing.T) { func TestDatabase_CreateRetentionPolicy_ErrRetentionPolicyExists(t *testing.T) {
s := OpenServer(NewMessagingClient()) s := OpenServer(NewMessagingClient())
defer s.Close() defer s.Close()
s.CreateDatabase("foo") s.CreateDatabase("foo")
s.Database("foo").CreateShardSpace(&influxdb.ShardSpace{Name: "bar"}) s.Database("foo").CreateRetentionPolicy(&influxdb.RetentionPolicy{Name: "bar"})
if err := s.Database("foo").CreateShardSpace(&influxdb.ShardSpace{Name: "bar"}); err != influxdb.ErrShardSpaceExists { if err := s.Database("foo").CreateRetentionPolicy(&influxdb.RetentionPolicy{Name: "bar"}); err != influxdb.ErrRetentionPolicyExists {
t.Fatal(err) t.Fatal(err)
} }
} }
// Ensure the server can delete an existing shard space. // Ensure the server can delete an existing retention policy.
func TestDatabase_DeleteShardSpace(t *testing.T) { func TestDatabase_DeleteRetentionPolicy(t *testing.T) {
s := OpenServer(NewMessagingClient()) s := OpenServer(NewMessagingClient())
defer s.Close() defer s.Close()
// Create a database and shard space. // Create a database and retention policy.
s.CreateDatabase("foo") s.CreateDatabase("foo")
db := s.Database("foo") db := s.Database("foo")
if err := db.CreateShardSpace(&influxdb.ShardSpace{Name: "bar"}); err != nil { if err := db.CreateRetentionPolicy(&influxdb.RetentionPolicy{Name: "bar"}); err != nil {
t.Fatal(err) t.Fatal(err)
} else if db.ShardSpace("bar") == nil { } else if db.RetentionPolicy("bar") == nil {
t.Fatal("shard space not created") t.Fatal("retention policy not created")
} }
// Remove shard space from database. // Remove retention policy from database.
if err := db.DeleteShardSpace("bar"); err != nil { if err := db.DeleteRetentionPolicy("bar"); err != nil {
t.Fatal(err) t.Fatal(err)
} else if db.ShardSpace("bar") != nil { } else if db.RetentionPolicy("bar") != nil {
t.Fatal("shard space not deleted") t.Fatal("retention policy not deleted")
} }
s.Restart() s.Restart()
if s.Database("foo").ShardSpace("bar") != nil { if s.Database("foo").RetentionPolicy("bar") != nil {
t.Fatal("shard space not deleted after restart") t.Fatal("retention policy not deleted after restart")
} }
} }
// Ensure the server returns an error when deleting a shard space after db is dropped. // Ensure the server returns an error when deleting a retention policy after db is dropped.
func TestDatabase_DeleteShardSpace_ErrDatabaseNotFound(t *testing.T) { func TestDatabase_DeleteRetentionPolicy_ErrDatabaseNotFound(t *testing.T) {
s := OpenServer(NewMessagingClient()) s := OpenServer(NewMessagingClient())
defer s.Close() defer s.Close()
@ -322,28 +319,74 @@ func TestDatabase_DeleteShardSpace_ErrDatabaseNotFound(t *testing.T) {
db := s.Database("foo") db := s.Database("foo")
s.DeleteDatabase("foo") s.DeleteDatabase("foo")
// Delete shard space on the database. // Delete retention policy on the database.
if err := db.DeleteShardSpace("bar"); err != influxdb.ErrDatabaseNotFound { if err := db.DeleteRetentionPolicy("bar"); err != influxdb.ErrDatabaseNotFound {
t.Fatal(err) t.Fatal(err)
} }
} }
// Ensure the server returns an error when deleting a shard space without a name. // Ensure the server returns an error when deleting a retention policy without a name.
func TestDatabase_DeleteShardSpace_ErrShardSpaceNameRequired(t *testing.T) { func TestDatabase_DeleteRetentionPolicy_ErrRetentionPolicyNameRequired(t *testing.T) {
s := OpenServer(NewMessagingClient()) s := OpenServer(NewMessagingClient())
defer s.Close() defer s.Close()
s.CreateDatabase("foo") s.CreateDatabase("foo")
if err := s.Database("foo").DeleteShardSpace(""); err != influxdb.ErrShardSpaceNameRequired { if err := s.Database("foo").DeleteRetentionPolicy(""); err != influxdb.ErrRetentionPolicyNameRequired {
t.Fatal(err) t.Fatal(err)
} }
} }
// Ensure the server returns an error when deleting a non-existent shard space. // Ensure the server returns an error when deleting a non-existent retention policy.
func TestDatabase_DeleteShardSpace_ErrShardSpaceNotFound(t *testing.T) { func TestDatabase_DeleteRetentionPolicy_ErrRetentionPolicyNotFound(t *testing.T) {
s := OpenServer(NewMessagingClient()) s := OpenServer(NewMessagingClient())
defer s.Close() defer s.Close()
s.CreateDatabase("foo") s.CreateDatabase("foo")
if err := s.Database("foo").DeleteShardSpace("no_such_space"); err != influxdb.ErrShardSpaceNotFound { if err := s.Database("foo").DeleteRetentionPolicy("no_such_policy"); err != influxdb.ErrRetentionPolicyNotFound {
t.Fatal(err)
}
}
// Ensure the server can set the default retention policy
func TestDatabase_SetDefaultRetentionPolicy(t *testing.T) {
s := OpenServer(NewMessagingClient())
defer s.Close()
s.CreateDatabase("foo")
db := s.Database("foo")
rp := &influxdb.RetentionPolicy{Name: "bar"}
if err := db.CreateRetentionPolicy(rp); err != nil {
t.Fatal(err)
} else if db.RetentionPolicy("bar") == nil {
t.Fatal("retention policy not created")
}
// Set bar as default
if err := db.SetDefaultRetentionPolicy("bar"); err != nil {
t.Fatal(err)
}
o := db.DefaultRetentionPolicy()
if o == nil {
t.Fatal("default policy not set")
} else if !reflect.DeepEqual(rp, o) {
t.Fatalf("retention policy mismatch: %#v", o)
}
s.Restart()
o = s.Database("foo").DefaultRetentionPolicy()
if o == nil {
t.Fatal("default policy not kept after restart")
} else if !reflect.DeepEqual(rp, o) {
t.Fatalf("retention policy mismatch after restart: %#v", o)
}
}
// Ensure the server returns an error when setting the deafult retention policy to a non-existant one.
func TestDatabase_SetDefaultRetentionPolicy_ErrRetentionPolicyNotFound(t *testing.T) {
s := OpenServer(NewMessagingClient())
defer s.Close()
s.CreateDatabase("foo")
if err := s.Database("foo").SetDefaultRetentionPolicy("no_such_policy"); err != influxdb.ErrRetentionPolicyNotFound {
t.Fatal(err) t.Fatal(err)
} }
} }
@ -357,7 +400,7 @@ func TestDatabase_WriteSeries(t *testing.T) {
defer s.Close() defer s.Close()
s.CreateDatabase("foo") s.CreateDatabase("foo")
db := s.Database("foo") db := s.Database("foo")
db.CreateShardSpace(&influxdb.ShardSpace{Name: "myspace", Duration: 1 * time.Hour}) db.CreateRetentionPolicys(&influxdb.RetentionPolicy{Name: "myspace", Duration: 1 * time.Hour})
db.CreateUser("susy", "pass", nil) db.CreateUser("susy", "pass", nil)
// Write series with one point to the database. // Write series with one point to the database.

View File

@ -57,15 +57,15 @@ func NewHandler(s *Server) *Handler {
h.mux.Get("/interfaces", http.HandlerFunc(h.serveInterfaces)) h.mux.Get("/interfaces", http.HandlerFunc(h.serveInterfaces))
// Shard routes. // Shard routes.
h.mux.Get("/cluster/shards", http.HandlerFunc(h.serveShards)) h.mux.Get("/db/:db/shards", http.HandlerFunc(h.serveShards))
h.mux.Post("/cluster/shards", http.HandlerFunc(h.serveCreateShard)) h.mux.Del("/db/:db/shards/:id", http.HandlerFunc(h.serveDeleteShard))
h.mux.Del("/cluster/shards/:id", http.HandlerFunc(h.serveDeleteShard))
// Shard space routes. // retention policy routes.
h.mux.Get("/cluster/shard_spaces", http.HandlerFunc(h.serveShardSpaces)) h.mux.Get("/db/:db/retention_policies", http.HandlerFunc(h.serveRetentionPolicys))
h.mux.Post("/cluster/shard_spaces/:db", http.HandlerFunc(h.serveCreateShardSpace)) h.mux.Get("/db/:db/retention_policies/:name/shards", http.HandlerFunc(h.serveShardsByRetentionPolicy))
h.mux.Post("/cluster/shard_spaces/:db/:name", http.HandlerFunc(h.serveUpdateShardSpace)) h.mux.Post("/db/:db/retention_policies", http.HandlerFunc(h.serveCreateRetentionPolicy))
h.mux.Del("/cluster/shard_spaces/:db/:name", http.HandlerFunc(h.serveDeleteShardSpace)) h.mux.Post("/db/:db/retention_policies/:name", http.HandlerFunc(h.serveUpdateRetentionPolicy))
h.mux.Del("/db/:db/retention_policies/:name", http.HandlerFunc(h.serveDeleteRetentionPolicy))
// Cluster config endpoints // Cluster config endpoints
h.mux.Get("/cluster/servers", http.HandlerFunc(h.serveServers)) h.mux.Get("/cluster/servers", http.HandlerFunc(h.serveServers))
@ -277,23 +277,23 @@ func (h *Handler) serveInterfaces(w http.ResponseWriter, r *http.Request) {}
// serveShards returns a list of shards. // serveShards returns a list of shards.
func (h *Handler) serveShards(w http.ResponseWriter, r *http.Request) {} func (h *Handler) serveShards(w http.ResponseWriter, r *http.Request) {}
// serveCreateShard creates a new shard. // serveShardsByRetentionPolicy returns a list of shards for a given retention policy.
func (h *Handler) serveCreateShard(w http.ResponseWriter, r *http.Request) {} func (h *Handler) serveShardsByRetentionPolicy(w http.ResponseWriter, r *http.Request) {}
// serveDeleteShard removes an existing shard. // serveDeleteShard removes an existing shard.
func (h *Handler) serveDeleteShard(w http.ResponseWriter, r *http.Request) {} func (h *Handler) serveDeleteShard(w http.ResponseWriter, r *http.Request) {}
// serveShardSpaces returns a list of shard spaces. // serveRetentionPolicys returns a list of retention policys.
func (h *Handler) serveShardSpaces(w http.ResponseWriter, r *http.Request) {} func (h *Handler) serveRetentionPolicys(w http.ResponseWriter, r *http.Request) {}
// serveCreateShardSpace creates a new shard space. // serveCreateRetentionPolicy creates a new retention policy.
func (h *Handler) serveCreateShardSpace(w http.ResponseWriter, r *http.Request) {} func (h *Handler) serveCreateRetentionPolicy(w http.ResponseWriter, r *http.Request) {}
// serveUpdateShardSpace updates an existing shard space. // serveUpdateRetentionPolicy updates an existing retention policy.
func (h *Handler) serveUpdateShardSpace(w http.ResponseWriter, r *http.Request) {} func (h *Handler) serveUpdateRetentionPolicy(w http.ResponseWriter, r *http.Request) {}
// serveDeleteShardSpace removes an existing shard space. // serveDeleteRetentionPolicy removes an existing retention policy.
func (h *Handler) serveDeleteShardSpace(w http.ResponseWriter, r *http.Request) {} func (h *Handler) serveDeleteRetentionPolicy(w http.ResponseWriter, r *http.Request) {}
// serveServers returns a list of servers in the cluster. // serveServers returns a list of servers in the cluster.
func (h *Handler) serveServers(w http.ResponseWriter, r *http.Request) {} func (h *Handler) serveServers(w http.ResponseWriter, r *http.Request) {}
@ -915,15 +915,15 @@ func (self *HTTPServer) convertShardsToMap(shards []*cluster.ShardData) []interf
return result return result
} }
func (self *HTTPServer) getShardSpaces(w libhttp.ResponseWriter, r *libhttp.Request) { func (self *HTTPServer) getRetentionPolicys(w libhttp.ResponseWriter, r *libhttp.Request) {
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) { self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
return libhttp.StatusOK, self.clusterConfig.GetShardSpaces() return libhttp.StatusOK, self.clusterConfig.GetRetentionPolicys()
}) })
} }
func (self *HTTPServer) createShardSpace(w libhttp.ResponseWriter, r *libhttp.Request) { func (self *HTTPServer) createRetentionPolicy(w libhttp.ResponseWriter, r *libhttp.Request) {
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) { self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
space := &cluster.ShardSpace{} space := &cluster.RetentionPolicy{}
decoder := json.NewDecoder(r.Body) decoder := json.NewDecoder(r.Body)
err := decoder.Decode(space) err := decoder.Decode(space)
if err != nil { if err != nil {
@ -934,7 +934,7 @@ func (self *HTTPServer) createShardSpace(w libhttp.ResponseWriter, r *libhttp.Re
if err != nil { if err != nil {
return libhttp.StatusBadRequest, err.Error() return libhttp.StatusBadRequest, err.Error()
} }
err = self.raftServer.CreateShardSpace(space) err = self.raftServer.CreateRetentionPolicy(space)
if err != nil { if err != nil {
return libhttp.StatusInternalServerError, err.Error() return libhttp.StatusInternalServerError, err.Error()
} }
@ -942,11 +942,11 @@ func (self *HTTPServer) createShardSpace(w libhttp.ResponseWriter, r *libhttp.Re
}) })
} }
func (self *HTTPServer) dropShardSpace(w libhttp.ResponseWriter, r *libhttp.Request) { func (self *HTTPServer) dropRetentionPolicy(w libhttp.ResponseWriter, r *libhttp.Request) {
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) { self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
name := r.URL.Query().Get(":name") name := r.URL.Query().Get(":name")
db := r.URL.Query().Get(":db") db := r.URL.Query().Get(":db")
if err := self.raftServer.DropShardSpace(db, name); err != nil { if err := self.raftServer.DropRetentionPolicy(db, name); err != nil {
return libhttp.StatusInternalServerError, err.Error() return libhttp.StatusInternalServerError, err.Error()
} }
return libhttp.StatusOK, nil return libhttp.StatusOK, nil
@ -954,7 +954,7 @@ func (self *HTTPServer) dropShardSpace(w libhttp.ResponseWriter, r *libhttp.Requ
} }
type DatabaseConfig struct { type DatabaseConfig struct {
Spaces []*cluster.ShardSpace `json:"spaces"` Spaces []*cluster.RetentionPolicy `json:"spaces"`
ContinuousQueries []string `json:"continuousQueries"` ContinuousQueries []string `json:"continuousQueries"`
} }
@ -981,7 +981,7 @@ func (self *HTTPServer) configureDatabase(w libhttp.ResponseWriter, r *libhttp.R
} }
} }
// validate shard spaces // validate retention policys
for _, space := range databaseConfig.Spaces { for _, space := range databaseConfig.Spaces {
err := space.Validate(self.clusterConfig, false) err := space.Validate(self.clusterConfig, false)
if err != nil { if err != nil {
@ -995,7 +995,7 @@ func (self *HTTPServer) configureDatabase(w libhttp.ResponseWriter, r *libhttp.R
} }
for _, space := range databaseConfig.Spaces { for _, space := range databaseConfig.Spaces {
space.Database = database space.Database = database
err = self.raftServer.CreateShardSpace(space) err = self.raftServer.CreateRetentionPolicy(space)
if err != nil { if err != nil {
return libhttp.StatusInternalServerError, err.Error() return libhttp.StatusInternalServerError, err.Error()
} }
@ -1010,9 +1010,9 @@ func (self *HTTPServer) configureDatabase(w libhttp.ResponseWriter, r *libhttp.R
}) })
} }
func (self *HTTPServer) updateShardSpace(w libhttp.ResponseWriter, r *libhttp.Request) { func (self *HTTPServer) updateRetentionPolicy(w libhttp.ResponseWriter, r *libhttp.Request) {
self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) { self.tryAsClusterAdmin(w, r, func(u User) (int, interface{}) {
space := &cluster.ShardSpace{} space := &cluster.RetentionPolicy{}
decoder := json.NewDecoder(r.Body) decoder := json.NewDecoder(r.Body)
err := decoder.Decode(space) err := decoder.Decode(space)
if err != nil { if err != nil {
@ -1021,13 +1021,13 @@ func (self *HTTPServer) updateShardSpace(w libhttp.ResponseWriter, r *libhttp.Re
space.Database = r.URL.Query().Get(":db") space.Database = r.URL.Query().Get(":db")
space.Name = r.URL.Query().Get(":name") space.Name = r.URL.Query().Get(":name")
if !self.clusterConfig.DatabaseExists(space.Database) { if !self.clusterConfig.DatabaseExists(space.Database) {
return libhttp.StatusNotAcceptable, "Can't update a shard space for a database that doesn't exist" return libhttp.StatusNotAcceptable, "Can't update a retention policy for a database that doesn't exist"
} }
if !self.clusterConfig.ShardSpaceExists(space) { if !self.clusterConfig.RetentionPolicyExists(space) {
return libhttp.StatusNotAcceptable, "Can't update a shard space that doesn't exist" return libhttp.StatusNotAcceptable, "Can't update a retention policy that doesn't exist"
} }
if err := self.raftServer.UpdateShardSpace(space); err != nil { if err := self.raftServer.UpdateRetentionPolicy(space); err != nil {
return libhttp.StatusInternalServerError, err.Error() return libhttp.StatusInternalServerError, err.Error()
} }
return libhttp.StatusOK, nil return libhttp.StatusOK, nil

View File

@ -41,14 +41,14 @@ var (
// ErrInvalidUsername is returned when using a username with invalid characters. // ErrInvalidUsername is returned when using a username with invalid characters.
ErrInvalidUsername = errors.New("invalid username") ErrInvalidUsername = errors.New("invalid username")
// ErrShardSpaceExists is returned when creating a duplicate shard space. // ErrRetentionPolicyExists is returned when creating a duplicate shard space.
ErrShardSpaceExists = errors.New("shard space exists") ErrRetentionPolicyExists = errors.New("retention policy exists")
// ErrShardSpaceNotFound is returned when deleting a non-existent shard space. // ErrRetentionPolicyNotFound is returned when deleting a non-existent shard space.
ErrShardSpaceNotFound = errors.New("shard space not found") ErrRetentionPolicyNotFound = errors.New("retention policy not found")
// ErrShardSpaceNameRequired is returned using a blank shard space name. // ErrRetentionPolicyNameRequired is returned using a blank shard space name.
ErrShardSpaceNameRequired = errors.New("shard space name required") ErrRetentionPolicyNameRequired = errors.New("retention policy name required")
// ErrShardNotFound is returned writing to a non-existent shard. // ErrShardNotFound is returned writing to a non-existent shard.
ErrShardNotFound = errors.New("shard not found") ErrShardNotFound = errors.New("shard not found")

View File

@ -19,8 +19,8 @@ const (
// It is also used when reseting the root user's password. // It is also used when reseting the root user's password.
DefaultRootPassword = "root" DefaultRootPassword = "root"
// DefaultShardSpaceName is the name of a databases's default shard space. // DefaultRetentionPolicyName is the name of a databases's default shard space.
DefaultShardSpaceName = "default" DefaultRetentionPolicyName = "default"
// DefaultSplitN represents the number of partitions a shard is split into. // DefaultSplitN represents the number of partitions a shard is split into.
DefaultSplitN = 1 DefaultSplitN = 1
@ -39,8 +39,8 @@ const (
// broadcast messages // broadcast messages
createDatabaseMessageType = messaging.MessageType(0x00) createDatabaseMessageType = messaging.MessageType(0x00)
deleteDatabaseMessageType = messaging.MessageType(0x01) deleteDatabaseMessageType = messaging.MessageType(0x01)
createShardSpaceMessageType = messaging.MessageType(0x02) createRetentionPolicyMessageType = messaging.MessageType(0x02)
deleteShardSpaceMessageType = messaging.MessageType(0x03) deleteRetentionPolicyMessageType = messaging.MessageType(0x03)
createClusterAdminMessageType = messaging.MessageType(0x04) createClusterAdminMessageType = messaging.MessageType(0x04)
deleteClusterAdminMessageType = messaging.MessageType(0x05) deleteClusterAdminMessageType = messaging.MessageType(0x05)
clusterAdminSetPasswordMessageType = messaging.MessageType(0x06) clusterAdminSetPasswordMessageType = messaging.MessageType(0x06)
@ -48,6 +48,7 @@ const (
deleteDBUserMessageType = messaging.MessageType(0x08) deleteDBUserMessageType = messaging.MessageType(0x08)
dbUserSetPasswordMessageType = messaging.MessageType(0x09) dbUserSetPasswordMessageType = messaging.MessageType(0x09)
createShardIfNotExistsMessageType = messaging.MessageType(0x0a) createShardIfNotExistsMessageType = messaging.MessageType(0x0a)
setDefaultRetentionPolicyMessageType = messaging.MessageType(0x0b)
// per-topic messages // per-topic messages
writeSeriesMessageType = messaging.MessageType(0x80) writeSeriesMessageType = messaging.MessageType(0x80)
@ -541,8 +542,8 @@ type deleteDBUserCommand struct {
Username string `json:"username"` Username string `json:"username"`
} }
func (s *Server) applyCreateShardSpace(m *messaging.Message) error { func (s *Server) applyCreateRetentionPolicy(m *messaging.Message) error {
var c createShardSpaceCommand var c createRetentionPolicyCommand
mustUnmarshalJSON(m.Data, &c) mustUnmarshalJSON(m.Data, &c)
s.mu.Lock() s.mu.Lock()
@ -554,7 +555,7 @@ func (s *Server) applyCreateShardSpace(m *messaging.Message) error {
return ErrDatabaseNotFound return ErrDatabaseNotFound
} }
if err := db.applyCreateShardSpace(c.Name, c.Regex, c.Retention, c.Duration, c.ReplicaN, c.SplitN); err != nil { if err := db.applyCreateRetentionPolicy(c.Name, c.Duration, c.ReplicaN, c.SplitN); err != nil {
return err return err
} }
@ -566,18 +567,16 @@ func (s *Server) applyCreateShardSpace(m *messaging.Message) error {
return nil return nil
} }
type createShardSpaceCommand struct { type createRetentionPolicyCommand struct {
Database string `json:"database"` Database string `json:"database"`
Name string `json:"name"` Name string `json:"name"`
Regex string `json:"regex"`
Retention time.Duration `json:"retention"`
Duration time.Duration `json:"duration"` Duration time.Duration `json:"duration"`
ReplicaN uint32 `json:"replicaN"` ReplicaN uint32 `json:"replicaN"`
SplitN uint32 `json:"splitN"` SplitN uint32 `json:"splitN"`
} }
func (s *Server) applyDeleteShardSpace(m *messaging.Message) error { func (s *Server) applyDeleteRetentionPolicy(m *messaging.Message) error {
var c deleteShardSpaceCommand var c deleteRetentionPolicyCommand
mustUnmarshalJSON(m.Data, &c) mustUnmarshalJSON(m.Data, &c)
s.mu.Lock() s.mu.Lock()
@ -590,7 +589,7 @@ func (s *Server) applyDeleteShardSpace(m *messaging.Message) error {
} }
// Remove shard space from database. // Remove shard space from database.
if err := db.applyDeleteShardSpace(c.Name); err != nil { if err := db.applyDeleteRetentionPolicy(c.Name); err != nil {
return err return err
} }
@ -602,7 +601,37 @@ func (s *Server) applyDeleteShardSpace(m *messaging.Message) error {
return nil return nil
} }
type deleteShardSpaceCommand struct { type deleteRetentionPolicyCommand struct {
Database string `json:"database"`
Name string `json:"name"`
}
func (s *Server) applySetDefaultRetentionPolicy(m *messaging.Message) error {
var c setDefaultRetentionPolicyCommand
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.applySetDefaultRetentionPolicy(c.Name); err != nil {
return err
}
// Persist to metastore.
s.meta.mustUpdate(func(tx *metatx) error {
return tx.saveDatabase(db)
})
return nil
}
type setDefaultRetentionPolicyCommand struct {
Database string `json:"database"` Database string `json:"database"`
Name string `json:"name"` Name string `json:"name"`
} }
@ -660,12 +689,14 @@ func (s *Server) processor(done chan struct{}) {
err = s.applyDeleteDBUser(m) err = s.applyDeleteDBUser(m)
case dbUserSetPasswordMessageType: case dbUserSetPasswordMessageType:
err = s.applyDBUserSetPassword(m) err = s.applyDBUserSetPassword(m)
case createShardSpaceMessageType: case createRetentionPolicyMessageType:
err = s.applyCreateShardSpace(m) err = s.applyCreateRetentionPolicy(m)
case deleteShardSpaceMessageType: case deleteRetentionPolicyMessageType:
err = s.applyDeleteShardSpace(m) err = s.applyDeleteRetentionPolicy(m)
case createShardIfNotExistsMessageType: case createShardIfNotExistsMessageType:
err = s.applyCreateShardIfNotExists(m) err = s.applyCreateShardIfNotExists(m)
case setDefaultRetentionPolicyMessageType:
err = s.applySetDefaultRetentionPolicy(m)
case writeSeriesMessageType: case writeSeriesMessageType:
/* TEMPORARILY REMOVED FOR PROTOBUFS. /* TEMPORARILY REMOVED FOR PROTOBUFS.
err = s.applyWriteSeries(m) err = s.applyWriteSeries(m)