1268 lines
33 KiB
Go
1268 lines
33 KiB
Go
package meta
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
"github.com/influxdb/influxdb/influxql"
|
|
"github.com/influxdb/influxdb/meta/internal"
|
|
)
|
|
|
|
//go:generate protoc --gogo_out=. internal/meta.proto
|
|
|
|
const (
|
|
// DefaultRetentionPolicyReplicaN is the default value of RetentionPolicyInfo.ReplicaN.
|
|
DefaultRetentionPolicyReplicaN = 1
|
|
|
|
// DefaultRetentionPolicyDuration is the default value of RetentionPolicyInfo.Duration.
|
|
DefaultRetentionPolicyDuration = 7 * (24 * time.Hour)
|
|
|
|
// MinRetentionPolicyDuration represents the minimum duration for a policy.
|
|
MinRetentionPolicyDuration = time.Hour
|
|
)
|
|
|
|
// Data represents the top level collection of all metadata.
|
|
type Data struct {
|
|
Term uint64 // associated raft term
|
|
Index uint64 // associated raft index
|
|
ClusterID uint64
|
|
Nodes []NodeInfo
|
|
Databases []DatabaseInfo
|
|
Users []UserInfo
|
|
|
|
MaxNodeID uint64
|
|
MaxShardGroupID uint64
|
|
MaxShardID uint64
|
|
}
|
|
|
|
// Node returns a node by id.
|
|
func (data *Data) Node(id uint64) *NodeInfo {
|
|
for i := range data.Nodes {
|
|
if data.Nodes[i].ID == id {
|
|
return &data.Nodes[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NodeByHost returns a node by hostname.
|
|
func (data *Data) NodeByHost(host string) *NodeInfo {
|
|
for i := range data.Nodes {
|
|
if data.Nodes[i].Host == host {
|
|
return &data.Nodes[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CreateNode adds a node to the metadata.
|
|
func (data *Data) CreateNode(host string) error {
|
|
// Ensure a node with the same host doesn't already exist.
|
|
if data.NodeByHost(host) != nil {
|
|
return ErrNodeExists
|
|
}
|
|
|
|
// Append new node.
|
|
data.MaxNodeID++
|
|
data.Nodes = append(data.Nodes, NodeInfo{
|
|
ID: data.MaxNodeID,
|
|
Host: host,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteNode removes a node from the metadata.
|
|
func (data *Data) DeleteNode(id uint64, force bool) error {
|
|
// Node has to be larger than 0 to be real
|
|
if id == 0 {
|
|
return ErrNodeIDRequired
|
|
}
|
|
// Is this a valid node?
|
|
nodeInfo := data.Node(id)
|
|
if nodeInfo == nil {
|
|
return ErrNodeNotFound
|
|
}
|
|
|
|
// Am I the only node? If so, nothing to do
|
|
if len(data.Nodes) == 1 {
|
|
return ErrNodeUnableToDropFinalNode
|
|
}
|
|
|
|
// Determine if there are any any non-replicated nodes and force was not specified
|
|
if !force {
|
|
for _, d := range data.Databases {
|
|
for _, rp := range d.RetentionPolicies {
|
|
// ignore replicated retention policies
|
|
if rp.ReplicaN > 1 {
|
|
continue
|
|
}
|
|
for _, sg := range rp.ShardGroups {
|
|
for _, s := range sg.Shards {
|
|
if s.OwnedBy(id) && len(s.Owners) == 1 {
|
|
return ErrShardNotReplicated
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove node id from all shard infos
|
|
for di, d := range data.Databases {
|
|
for ri, rp := range d.RetentionPolicies {
|
|
for sgi, sg := range rp.ShardGroups {
|
|
for si, s := range sg.Shards {
|
|
if s.OwnedBy(id) {
|
|
var owners []ShardOwner
|
|
for _, o := range s.Owners {
|
|
if o.NodeID != id {
|
|
owners = append(owners, o)
|
|
}
|
|
}
|
|
data.Databases[di].RetentionPolicies[ri].ShardGroups[sgi].Shards[si].Owners = owners
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove this node from the in memory nodes
|
|
var nodes []NodeInfo
|
|
for _, n := range data.Nodes {
|
|
if n.ID == id {
|
|
continue
|
|
}
|
|
nodes = append(nodes, n)
|
|
}
|
|
data.Nodes = nodes
|
|
|
|
return nil
|
|
}
|
|
|
|
// Database returns a database by name.
|
|
func (data *Data) Database(name string) *DatabaseInfo {
|
|
for i := range data.Databases {
|
|
if data.Databases[i].Name == name {
|
|
return &data.Databases[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CreateDatabase creates a new database.
|
|
// Returns an error if name is blank or if a database with the same name already exists.
|
|
func (data *Data) CreateDatabase(name string) error {
|
|
if name == "" {
|
|
return ErrDatabaseNameRequired
|
|
} else if data.Database(name) != nil {
|
|
return ErrDatabaseExists
|
|
}
|
|
|
|
// Append new node.
|
|
data.Databases = append(data.Databases, DatabaseInfo{Name: name})
|
|
|
|
return nil
|
|
}
|
|
|
|
// DropDatabase removes a database by name.
|
|
func (data *Data) DropDatabase(name string) error {
|
|
for i := range data.Databases {
|
|
if data.Databases[i].Name == name {
|
|
data.Databases = append(data.Databases[:i], data.Databases[i+1:]...)
|
|
return nil
|
|
}
|
|
}
|
|
return ErrDatabaseNotFound
|
|
}
|
|
|
|
// RenameDatabase renames a database.
|
|
// Returns an error if oldName or newName is blank
|
|
// or if a database with the newName already exists
|
|
// or if a database with oldName does not exist
|
|
func (data *Data) RenameDatabase(oldName, newName string) error {
|
|
if newName == "" || oldName == "" {
|
|
return ErrDatabaseNameRequired
|
|
}
|
|
if data.Database(newName) != nil {
|
|
return ErrDatabaseExists
|
|
}
|
|
if data.Database(oldName) == nil {
|
|
return ErrDatabaseNotFound
|
|
}
|
|
// TODO should rename database in continuous queries also
|
|
// for now, just return an error if there is a possible conflict
|
|
if data.isDatabaseNameUsedInCQ(oldName) {
|
|
return ErrDatabaseRenameCQConflict
|
|
}
|
|
// find database named oldName and rename it to newName
|
|
for i := range data.Databases {
|
|
if data.Databases[i].Name == oldName {
|
|
data.Databases[i].Name = newName
|
|
data.switchDatabaseUserPrivileges(oldName, newName)
|
|
return nil
|
|
}
|
|
}
|
|
return ErrDatabaseNotFound
|
|
}
|
|
|
|
// isDatabaseNameUsedInCQ returns true if a database name is used in any continuous query
|
|
func (data *Data) isDatabaseNameUsedInCQ(dbName string) bool {
|
|
CQOnDb := fmt.Sprintf(" ON %s ", dbName)
|
|
CQIntoDb := fmt.Sprintf(" INTO \"%s\".", dbName)
|
|
CQFromDb := fmt.Sprintf(" FROM \"%s\".", dbName)
|
|
for i := range data.Databases {
|
|
for j := range data.Databases[i].ContinuousQueries {
|
|
query := data.Databases[i].ContinuousQueries[j].Query
|
|
if strings.Contains(query, CQOnDb) {
|
|
return true
|
|
}
|
|
if strings.Contains(query, CQIntoDb) {
|
|
return true
|
|
}
|
|
if strings.Contains(query, CQFromDb) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// switchDatabaseUserPrivileges changes the database associated with user privileges
|
|
func (data *Data) switchDatabaseUserPrivileges(oldDatabase, newDatabase string) error {
|
|
for i := range data.Users {
|
|
if p, ok := data.Users[i].Privileges[oldDatabase]; ok {
|
|
data.Users[i].Privileges[newDatabase] = p
|
|
delete(data.Users[i].Privileges, oldDatabase)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RetentionPolicy returns a retention policy for a database by name.
|
|
func (data *Data) RetentionPolicy(database, name string) (*RetentionPolicyInfo, error) {
|
|
di := data.Database(database)
|
|
if di == nil {
|
|
return nil, ErrDatabaseNotFound
|
|
}
|
|
|
|
for i := range di.RetentionPolicies {
|
|
if di.RetentionPolicies[i].Name == name {
|
|
return &di.RetentionPolicies[i], nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// CreateRetentionPolicy creates a new retention policy on a database.
|
|
// Returns an error if name is blank or if a database does not exist.
|
|
func (data *Data) CreateRetentionPolicy(database string, rpi *RetentionPolicyInfo) error {
|
|
// Validate retention policy.
|
|
if rpi.Name == "" {
|
|
return ErrRetentionPolicyNameRequired
|
|
} else if rpi.ReplicaN < 1 {
|
|
return ErrReplicationFactorTooLow
|
|
}
|
|
|
|
// Find database.
|
|
di := data.Database(database)
|
|
if di == nil {
|
|
return ErrDatabaseNotFound
|
|
} else if di.RetentionPolicy(rpi.Name) != nil {
|
|
return ErrRetentionPolicyExists
|
|
}
|
|
|
|
// Append new policy.
|
|
di.RetentionPolicies = append(di.RetentionPolicies, RetentionPolicyInfo{
|
|
Name: rpi.Name,
|
|
Duration: rpi.Duration,
|
|
ShardGroupDuration: shardGroupDuration(rpi.Duration),
|
|
ReplicaN: rpi.ReplicaN,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// DropRetentionPolicy removes a retention policy from a database by name.
|
|
func (data *Data) DropRetentionPolicy(database, name string) error {
|
|
// Find database.
|
|
di := data.Database(database)
|
|
if di == nil {
|
|
return ErrDatabaseNotFound
|
|
}
|
|
|
|
// Prohibit dropping the default retention policy.
|
|
if di.DefaultRetentionPolicy == name {
|
|
return ErrRetentionPolicyDefault
|
|
}
|
|
|
|
// Remove from list.
|
|
for i := range di.RetentionPolicies {
|
|
if di.RetentionPolicies[i].Name == name {
|
|
di.RetentionPolicies = append(di.RetentionPolicies[:i], di.RetentionPolicies[i+1:]...)
|
|
return nil
|
|
}
|
|
}
|
|
return ErrRetentionPolicyNotFound
|
|
}
|
|
|
|
// UpdateRetentionPolicy updates an existing retention policy.
|
|
func (data *Data) UpdateRetentionPolicy(database, name string, rpu *RetentionPolicyUpdate) error {
|
|
// Find database.
|
|
di := data.Database(database)
|
|
if di == nil {
|
|
return ErrDatabaseNotFound
|
|
}
|
|
|
|
// Find policy.
|
|
rpi := di.RetentionPolicy(name)
|
|
if rpi == nil {
|
|
return ErrRetentionPolicyNotFound
|
|
}
|
|
|
|
// Ensure new policy doesn't match an existing policy.
|
|
if rpu.Name != nil && *rpu.Name != name && di.RetentionPolicy(*rpu.Name) != nil {
|
|
return ErrRetentionPolicyNameExists
|
|
}
|
|
|
|
// Enforce duration of at least MinRetentionPolicyDuration
|
|
if rpu.Duration != nil && *rpu.Duration < MinRetentionPolicyDuration && *rpu.Duration != 0 {
|
|
return ErrRetentionPolicyDurationTooLow
|
|
}
|
|
|
|
// Update fields.
|
|
if rpu.Name != nil {
|
|
rpi.Name = *rpu.Name
|
|
}
|
|
if rpu.Duration != nil {
|
|
rpi.Duration = *rpu.Duration
|
|
rpi.ShardGroupDuration = shardGroupDuration(rpi.Duration)
|
|
}
|
|
if rpu.ReplicaN != nil {
|
|
rpi.ReplicaN = *rpu.ReplicaN
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetDefaultRetentionPolicy sets the default retention policy for a database.
|
|
func (data *Data) SetDefaultRetentionPolicy(database, name string) error {
|
|
// Find database and verify policy exists.
|
|
di := data.Database(database)
|
|
if di == nil {
|
|
return ErrDatabaseNotFound
|
|
} else if di.RetentionPolicy(name) == nil {
|
|
return ErrRetentionPolicyNotFound
|
|
}
|
|
|
|
// Set default policy.
|
|
di.DefaultRetentionPolicy = name
|
|
|
|
return nil
|
|
}
|
|
|
|
// ShardGroup returns a list of all shard groups on a database and policy.
|
|
func (data *Data) ShardGroups(database, policy string) ([]ShardGroupInfo, error) {
|
|
// Find retention policy.
|
|
rpi, err := data.RetentionPolicy(database, policy)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if rpi == nil {
|
|
return nil, ErrRetentionPolicyNotFound
|
|
}
|
|
groups := make([]ShardGroupInfo, 0, len(rpi.ShardGroups))
|
|
for _, g := range rpi.ShardGroups {
|
|
if g.Deleted() {
|
|
continue
|
|
}
|
|
groups = append(groups, g)
|
|
}
|
|
return groups, nil
|
|
}
|
|
|
|
// ShardGroupsByTimeRange returns a list of all shard groups on a database and policy that may contain data
|
|
// for the specified time range. Shard groups are sorted by start time.
|
|
func (data *Data) ShardGroupsByTimeRange(database, policy string, tmin, tmax time.Time) ([]ShardGroupInfo, error) {
|
|
// Find retention policy.
|
|
rpi, err := data.RetentionPolicy(database, policy)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if rpi == nil {
|
|
return nil, ErrRetentionPolicyNotFound
|
|
}
|
|
groups := make([]ShardGroupInfo, 0, len(rpi.ShardGroups))
|
|
for _, g := range rpi.ShardGroups {
|
|
if g.Deleted() || !g.Overlaps(tmin, tmax) {
|
|
continue
|
|
}
|
|
groups = append(groups, g)
|
|
}
|
|
return groups, nil
|
|
}
|
|
|
|
// ShardGroupByTimestamp returns the shard group on a database and policy for a given timestamp.
|
|
func (data *Data) ShardGroupByTimestamp(database, policy string, timestamp time.Time) (*ShardGroupInfo, error) {
|
|
// Find retention policy.
|
|
rpi, err := data.RetentionPolicy(database, policy)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if rpi == nil {
|
|
return nil, ErrRetentionPolicyNotFound
|
|
}
|
|
|
|
return rpi.ShardGroupByTimestamp(timestamp), nil
|
|
}
|
|
|
|
// CreateShardGroup creates a shard group on a database and policy for a given timestamp.
|
|
func (data *Data) CreateShardGroup(database, policy string, timestamp time.Time) error {
|
|
// Ensure there are nodes in the metadata.
|
|
if len(data.Nodes) == 0 {
|
|
return ErrNodesRequired
|
|
}
|
|
|
|
// Find retention policy.
|
|
rpi, err := data.RetentionPolicy(database, policy)
|
|
if err != nil {
|
|
return err
|
|
} else if rpi == nil {
|
|
return ErrRetentionPolicyNotFound
|
|
}
|
|
|
|
// Verify that shard group doesn't already exist for this timestamp.
|
|
if rpi.ShardGroupByTimestamp(timestamp) != nil {
|
|
return ErrShardGroupExists
|
|
}
|
|
|
|
// Require at least one replica but no more replicas than nodes.
|
|
replicaN := rpi.ReplicaN
|
|
if replicaN == 0 {
|
|
replicaN = 1
|
|
} else if replicaN > len(data.Nodes) {
|
|
replicaN = len(data.Nodes)
|
|
}
|
|
|
|
// Determine shard count by node count divided by replication factor.
|
|
// This will ensure nodes will get distributed across nodes evenly and
|
|
// replicated the correct number of times.
|
|
shardN := len(data.Nodes) / replicaN
|
|
|
|
// Create the shard group.
|
|
data.MaxShardGroupID++
|
|
sgi := ShardGroupInfo{}
|
|
sgi.ID = data.MaxShardGroupID
|
|
sgi.StartTime = timestamp.Truncate(rpi.ShardGroupDuration).UTC()
|
|
sgi.EndTime = sgi.StartTime.Add(rpi.ShardGroupDuration).UTC()
|
|
|
|
// Create shards on the group.
|
|
sgi.Shards = make([]ShardInfo, shardN)
|
|
for i := range sgi.Shards {
|
|
data.MaxShardID++
|
|
sgi.Shards[i] = ShardInfo{ID: data.MaxShardID}
|
|
}
|
|
|
|
// Assign data nodes to shards via round robin.
|
|
// Start from a repeatably "random" place in the node list.
|
|
nodeIndex := int(data.Index % uint64(len(data.Nodes)))
|
|
for i := range sgi.Shards {
|
|
si := &sgi.Shards[i]
|
|
for j := 0; j < replicaN; j++ {
|
|
nodeID := data.Nodes[nodeIndex%len(data.Nodes)].ID
|
|
si.Owners = append(si.Owners, ShardOwner{NodeID: nodeID})
|
|
nodeIndex++
|
|
}
|
|
}
|
|
|
|
// Retention policy has a new shard group, so update the policy. Shard
|
|
// Groups must be stored in sorted order, as other parts of the system
|
|
// assume this to be the case.
|
|
rpi.ShardGroups = append(rpi.ShardGroups, sgi)
|
|
sort.Sort(ShardGroupInfos(rpi.ShardGroups))
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteShardGroup removes a shard group from a database and retention policy by id.
|
|
func (data *Data) DeleteShardGroup(database, policy string, id uint64) error {
|
|
// Find retention policy.
|
|
rpi, err := data.RetentionPolicy(database, policy)
|
|
if err != nil {
|
|
return err
|
|
} else if rpi == nil {
|
|
return ErrRetentionPolicyNotFound
|
|
}
|
|
|
|
// Find shard group by ID and set its deletion timestamp.
|
|
for i := range rpi.ShardGroups {
|
|
if rpi.ShardGroups[i].ID == id {
|
|
rpi.ShardGroups[i].DeletedAt = time.Now().UTC()
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return ErrShardGroupNotFound
|
|
}
|
|
|
|
// CreateContinuousQuery adds a named continuous query to a database.
|
|
func (data *Data) CreateContinuousQuery(database, name, query string) error {
|
|
di := data.Database(database)
|
|
if di == nil {
|
|
return ErrDatabaseNotFound
|
|
}
|
|
|
|
// Ensure the name doesn't already exist.
|
|
for i := range di.ContinuousQueries {
|
|
if di.ContinuousQueries[i].Name == name {
|
|
return ErrContinuousQueryExists
|
|
}
|
|
}
|
|
|
|
// Append new query.
|
|
di.ContinuousQueries = append(di.ContinuousQueries, ContinuousQueryInfo{
|
|
Name: name,
|
|
Query: query,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// DropContinuousQuery removes a continuous query.
|
|
func (data *Data) DropContinuousQuery(database, name string) error {
|
|
di := data.Database(database)
|
|
if di == nil {
|
|
return ErrDatabaseNotFound
|
|
}
|
|
|
|
for i := range di.ContinuousQueries {
|
|
if di.ContinuousQueries[i].Name == name {
|
|
di.ContinuousQueries = append(di.ContinuousQueries[:i], di.ContinuousQueries[i+1:]...)
|
|
return nil
|
|
}
|
|
}
|
|
return ErrContinuousQueryNotFound
|
|
}
|
|
|
|
// User returns a user by username.
|
|
func (data *Data) User(username string) *UserInfo {
|
|
for i := range data.Users {
|
|
if data.Users[i].Name == username {
|
|
return &data.Users[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CreateUser creates a new user.
|
|
func (data *Data) CreateUser(name, hash string, admin bool) error {
|
|
// Ensure the user doesn't already exist.
|
|
if name == "" {
|
|
return ErrUsernameRequired
|
|
} else if data.User(name) != nil {
|
|
return ErrUserExists
|
|
}
|
|
|
|
// Append new user.
|
|
data.Users = append(data.Users, UserInfo{
|
|
Name: name,
|
|
Hash: hash,
|
|
Admin: admin,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// DropUser removes an existing user by name.
|
|
func (data *Data) DropUser(name string) error {
|
|
for i := range data.Users {
|
|
if data.Users[i].Name == name {
|
|
data.Users = append(data.Users[:i], data.Users[i+1:]...)
|
|
return nil
|
|
}
|
|
}
|
|
return ErrUserNotFound
|
|
}
|
|
|
|
// UpdateUser updates the password hash of an existing user.
|
|
func (data *Data) UpdateUser(name, hash string) error {
|
|
for i := range data.Users {
|
|
if data.Users[i].Name == name {
|
|
data.Users[i].Hash = hash
|
|
return nil
|
|
}
|
|
}
|
|
return ErrUserNotFound
|
|
}
|
|
|
|
// SetPrivilege sets a privilege for a user on a database.
|
|
func (data *Data) SetPrivilege(name, database string, p influxql.Privilege) error {
|
|
ui := data.User(name)
|
|
if ui == nil {
|
|
return ErrUserNotFound
|
|
}
|
|
|
|
if ui.Privileges == nil {
|
|
ui.Privileges = make(map[string]influxql.Privilege)
|
|
}
|
|
ui.Privileges[database] = p
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetAdminPrivilege sets the admin privilege for a user.
|
|
func (data *Data) SetAdminPrivilege(name string, admin bool) error {
|
|
ui := data.User(name)
|
|
if ui == nil {
|
|
return ErrUserNotFound
|
|
}
|
|
|
|
ui.Admin = admin
|
|
|
|
return nil
|
|
}
|
|
|
|
// UserPrivileges gets the privileges for a user.
|
|
func (data *Data) UserPrivileges(name string) (map[string]influxql.Privilege, error) {
|
|
ui := data.User(name)
|
|
if ui == nil {
|
|
return nil, ErrUserNotFound
|
|
}
|
|
|
|
return ui.Privileges, nil
|
|
}
|
|
|
|
// UserPrivilege gets the privilege for a user on a database.
|
|
func (data *Data) UserPrivilege(name, database string) (*influxql.Privilege, error) {
|
|
ui := data.User(name)
|
|
if ui == nil {
|
|
return nil, ErrUserNotFound
|
|
}
|
|
|
|
for db, p := range ui.Privileges {
|
|
if db == database {
|
|
return &p, nil
|
|
}
|
|
}
|
|
|
|
return influxql.NewPrivilege(influxql.NoPrivileges), nil
|
|
}
|
|
|
|
// Clone returns a copy of data with a new version.
|
|
func (data *Data) Clone() *Data {
|
|
other := *data
|
|
|
|
// Copy nodes.
|
|
if data.Nodes != nil {
|
|
other.Nodes = make([]NodeInfo, len(data.Nodes))
|
|
for i := range data.Nodes {
|
|
other.Nodes[i] = data.Nodes[i].clone()
|
|
}
|
|
}
|
|
|
|
// Deep copy databases.
|
|
if data.Databases != nil {
|
|
other.Databases = make([]DatabaseInfo, len(data.Databases))
|
|
for i := range data.Databases {
|
|
other.Databases[i] = data.Databases[i].clone()
|
|
}
|
|
}
|
|
|
|
// Copy users.
|
|
if data.Users != nil {
|
|
other.Users = make([]UserInfo, len(data.Users))
|
|
for i := range data.Users {
|
|
other.Users[i] = data.Users[i].clone()
|
|
}
|
|
}
|
|
|
|
return &other
|
|
}
|
|
|
|
// marshal serializes to a protobuf representation.
|
|
func (data *Data) marshal() *internal.Data {
|
|
pb := &internal.Data{
|
|
Term: proto.Uint64(data.Term),
|
|
Index: proto.Uint64(data.Index),
|
|
ClusterID: proto.Uint64(data.ClusterID),
|
|
|
|
MaxNodeID: proto.Uint64(data.MaxNodeID),
|
|
MaxShardGroupID: proto.Uint64(data.MaxShardGroupID),
|
|
MaxShardID: proto.Uint64(data.MaxShardID),
|
|
}
|
|
|
|
pb.Nodes = make([]*internal.NodeInfo, len(data.Nodes))
|
|
for i := range data.Nodes {
|
|
pb.Nodes[i] = data.Nodes[i].marshal()
|
|
}
|
|
|
|
pb.Databases = make([]*internal.DatabaseInfo, len(data.Databases))
|
|
for i := range data.Databases {
|
|
pb.Databases[i] = data.Databases[i].marshal()
|
|
}
|
|
|
|
pb.Users = make([]*internal.UserInfo, len(data.Users))
|
|
for i := range data.Users {
|
|
pb.Users[i] = data.Users[i].marshal()
|
|
}
|
|
|
|
return pb
|
|
}
|
|
|
|
// unmarshal deserializes from a protobuf representation.
|
|
func (data *Data) unmarshal(pb *internal.Data) {
|
|
data.Term = pb.GetTerm()
|
|
data.Index = pb.GetIndex()
|
|
data.ClusterID = pb.GetClusterID()
|
|
|
|
data.MaxNodeID = pb.GetMaxNodeID()
|
|
data.MaxShardGroupID = pb.GetMaxShardGroupID()
|
|
data.MaxShardID = pb.GetMaxShardID()
|
|
|
|
data.Nodes = make([]NodeInfo, len(pb.GetNodes()))
|
|
for i, x := range pb.GetNodes() {
|
|
data.Nodes[i].unmarshal(x)
|
|
}
|
|
|
|
data.Databases = make([]DatabaseInfo, len(pb.GetDatabases()))
|
|
for i, x := range pb.GetDatabases() {
|
|
data.Databases[i].unmarshal(x)
|
|
}
|
|
|
|
data.Users = make([]UserInfo, len(pb.GetUsers()))
|
|
for i, x := range pb.GetUsers() {
|
|
data.Users[i].unmarshal(x)
|
|
}
|
|
}
|
|
|
|
// MarshalBinary encodes the metadata to a binary format.
|
|
func (data *Data) MarshalBinary() ([]byte, error) {
|
|
return proto.Marshal(data.marshal())
|
|
}
|
|
|
|
// UnmarshalBinary decodes the object from a binary format.
|
|
func (data *Data) UnmarshalBinary(buf []byte) error {
|
|
var pb internal.Data
|
|
if err := proto.Unmarshal(buf, &pb); err != nil {
|
|
return err
|
|
}
|
|
data.unmarshal(&pb)
|
|
return nil
|
|
}
|
|
|
|
// NodeInfo represents information about a single node in the cluster.
|
|
type NodeInfo struct {
|
|
ID uint64
|
|
Host string
|
|
}
|
|
|
|
// clone returns a deep copy of ni.
|
|
func (ni NodeInfo) clone() NodeInfo { return ni }
|
|
|
|
// marshal serializes to a protobuf representation.
|
|
func (ni NodeInfo) marshal() *internal.NodeInfo {
|
|
pb := &internal.NodeInfo{}
|
|
pb.ID = proto.Uint64(ni.ID)
|
|
pb.Host = proto.String(ni.Host)
|
|
return pb
|
|
}
|
|
|
|
// unmarshal deserializes from a protobuf representation.
|
|
func (ni *NodeInfo) unmarshal(pb *internal.NodeInfo) {
|
|
ni.ID = pb.GetID()
|
|
ni.Host = pb.GetHost()
|
|
}
|
|
|
|
// DatabaseInfo represents information about a database in the system.
|
|
type DatabaseInfo struct {
|
|
Name string
|
|
DefaultRetentionPolicy string
|
|
RetentionPolicies []RetentionPolicyInfo
|
|
ContinuousQueries []ContinuousQueryInfo
|
|
}
|
|
|
|
// RetentionPolicy returns a retention policy by name.
|
|
func (di DatabaseInfo) RetentionPolicy(name string) *RetentionPolicyInfo {
|
|
for i := range di.RetentionPolicies {
|
|
if di.RetentionPolicies[i].Name == name {
|
|
return &di.RetentionPolicies[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ShardInfos returns a list of all shards' info for the database.
|
|
func (di DatabaseInfo) ShardInfos() []ShardInfo {
|
|
shards := map[uint64]*ShardInfo{}
|
|
for i := range di.RetentionPolicies {
|
|
for j := range di.RetentionPolicies[i].ShardGroups {
|
|
sg := di.RetentionPolicies[i].ShardGroups[j]
|
|
// Skip deleted shard groups
|
|
if sg.Deleted() {
|
|
continue
|
|
}
|
|
for k := range sg.Shards {
|
|
si := &di.RetentionPolicies[i].ShardGroups[j].Shards[k]
|
|
shards[si.ID] = si
|
|
}
|
|
}
|
|
}
|
|
|
|
infos := make([]ShardInfo, 0, len(shards))
|
|
for _, info := range shards {
|
|
infos = append(infos, *info)
|
|
}
|
|
|
|
return infos
|
|
}
|
|
|
|
// clone returns a deep copy of di.
|
|
func (di DatabaseInfo) clone() DatabaseInfo {
|
|
other := di
|
|
|
|
if di.RetentionPolicies != nil {
|
|
other.RetentionPolicies = make([]RetentionPolicyInfo, len(di.RetentionPolicies))
|
|
for i := range di.RetentionPolicies {
|
|
other.RetentionPolicies[i] = di.RetentionPolicies[i].clone()
|
|
}
|
|
}
|
|
|
|
// Copy continuous queries.
|
|
if di.ContinuousQueries != nil {
|
|
other.ContinuousQueries = make([]ContinuousQueryInfo, len(di.ContinuousQueries))
|
|
for i := range di.ContinuousQueries {
|
|
other.ContinuousQueries[i] = di.ContinuousQueries[i].clone()
|
|
}
|
|
}
|
|
|
|
return other
|
|
}
|
|
|
|
// marshal serializes to a protobuf representation.
|
|
func (di DatabaseInfo) marshal() *internal.DatabaseInfo {
|
|
pb := &internal.DatabaseInfo{}
|
|
pb.Name = proto.String(di.Name)
|
|
pb.DefaultRetentionPolicy = proto.String(di.DefaultRetentionPolicy)
|
|
|
|
pb.RetentionPolicies = make([]*internal.RetentionPolicyInfo, len(di.RetentionPolicies))
|
|
for i := range di.RetentionPolicies {
|
|
pb.RetentionPolicies[i] = di.RetentionPolicies[i].marshal()
|
|
}
|
|
|
|
pb.ContinuousQueries = make([]*internal.ContinuousQueryInfo, len(di.ContinuousQueries))
|
|
for i := range di.ContinuousQueries {
|
|
pb.ContinuousQueries[i] = di.ContinuousQueries[i].marshal()
|
|
}
|
|
return pb
|
|
}
|
|
|
|
// unmarshal deserializes from a protobuf representation.
|
|
func (di *DatabaseInfo) unmarshal(pb *internal.DatabaseInfo) {
|
|
di.Name = pb.GetName()
|
|
di.DefaultRetentionPolicy = pb.GetDefaultRetentionPolicy()
|
|
|
|
if len(pb.GetRetentionPolicies()) > 0 {
|
|
di.RetentionPolicies = make([]RetentionPolicyInfo, len(pb.GetRetentionPolicies()))
|
|
for i, x := range pb.GetRetentionPolicies() {
|
|
di.RetentionPolicies[i].unmarshal(x)
|
|
}
|
|
}
|
|
|
|
if len(pb.GetContinuousQueries()) > 0 {
|
|
di.ContinuousQueries = make([]ContinuousQueryInfo, len(pb.GetContinuousQueries()))
|
|
for i, x := range pb.GetContinuousQueries() {
|
|
di.ContinuousQueries[i].unmarshal(x)
|
|
}
|
|
}
|
|
}
|
|
|
|
// RetentionPolicyInfo represents metadata about a retention policy.
|
|
type RetentionPolicyInfo struct {
|
|
Name string
|
|
ReplicaN int
|
|
Duration time.Duration
|
|
ShardGroupDuration time.Duration
|
|
ShardGroups []ShardGroupInfo
|
|
}
|
|
|
|
// NewRetentionPolicyInfo returns a new instance of RetentionPolicyInfo with defaults set.
|
|
func NewRetentionPolicyInfo(name string) *RetentionPolicyInfo {
|
|
return &RetentionPolicyInfo{
|
|
Name: name,
|
|
ReplicaN: DefaultRetentionPolicyReplicaN,
|
|
Duration: DefaultRetentionPolicyDuration,
|
|
}
|
|
}
|
|
|
|
// ShardGroupByTimestamp returns the shard group in the policy that contains the timestamp.
|
|
func (rpi *RetentionPolicyInfo) ShardGroupByTimestamp(timestamp time.Time) *ShardGroupInfo {
|
|
for i := range rpi.ShardGroups {
|
|
if rpi.ShardGroups[i].Contains(timestamp) && !rpi.ShardGroups[i].Deleted() {
|
|
return &rpi.ShardGroups[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ExpiredShardGroups returns the Shard Groups which are considered expired, for the given time.
|
|
func (rpi *RetentionPolicyInfo) ExpiredShardGroups(t time.Time) []*ShardGroupInfo {
|
|
groups := make([]*ShardGroupInfo, 0)
|
|
for i := range rpi.ShardGroups {
|
|
if rpi.ShardGroups[i].Deleted() {
|
|
continue
|
|
}
|
|
if rpi.Duration != 0 && rpi.ShardGroups[i].EndTime.Add(rpi.Duration).Before(t) {
|
|
groups = append(groups, &rpi.ShardGroups[i])
|
|
}
|
|
}
|
|
return groups
|
|
}
|
|
|
|
// DeletedShardGroups returns the Shard Groups which are marked as deleted.
|
|
func (rpi *RetentionPolicyInfo) DeletedShardGroups() []*ShardGroupInfo {
|
|
groups := make([]*ShardGroupInfo, 0)
|
|
for i := range rpi.ShardGroups {
|
|
if rpi.ShardGroups[i].Deleted() {
|
|
groups = append(groups, &rpi.ShardGroups[i])
|
|
}
|
|
}
|
|
return groups
|
|
}
|
|
|
|
// marshal serializes to a protobuf representation.
|
|
func (rpi *RetentionPolicyInfo) marshal() *internal.RetentionPolicyInfo {
|
|
pb := &internal.RetentionPolicyInfo{
|
|
Name: proto.String(rpi.Name),
|
|
ReplicaN: proto.Uint32(uint32(rpi.ReplicaN)),
|
|
Duration: proto.Int64(int64(rpi.Duration)),
|
|
ShardGroupDuration: proto.Int64(int64(rpi.ShardGroupDuration)),
|
|
}
|
|
|
|
pb.ShardGroups = make([]*internal.ShardGroupInfo, len(rpi.ShardGroups))
|
|
for i, sgi := range rpi.ShardGroups {
|
|
pb.ShardGroups[i] = sgi.marshal()
|
|
}
|
|
|
|
return pb
|
|
}
|
|
|
|
// unmarshal deserializes from a protobuf representation.
|
|
func (rpi *RetentionPolicyInfo) unmarshal(pb *internal.RetentionPolicyInfo) {
|
|
rpi.Name = pb.GetName()
|
|
rpi.ReplicaN = int(pb.GetReplicaN())
|
|
rpi.Duration = time.Duration(pb.GetDuration())
|
|
rpi.ShardGroupDuration = time.Duration(pb.GetShardGroupDuration())
|
|
|
|
if len(pb.GetShardGroups()) > 0 {
|
|
rpi.ShardGroups = make([]ShardGroupInfo, len(pb.GetShardGroups()))
|
|
for i, x := range pb.GetShardGroups() {
|
|
rpi.ShardGroups[i].unmarshal(x)
|
|
}
|
|
}
|
|
}
|
|
|
|
// clone returns a deep copy of rpi.
|
|
func (rpi RetentionPolicyInfo) clone() RetentionPolicyInfo {
|
|
other := rpi
|
|
|
|
if rpi.ShardGroups != nil {
|
|
other.ShardGroups = make([]ShardGroupInfo, len(rpi.ShardGroups))
|
|
for i := range rpi.ShardGroups {
|
|
other.ShardGroups[i] = rpi.ShardGroups[i].clone()
|
|
}
|
|
}
|
|
|
|
return other
|
|
}
|
|
|
|
// shardGroupDuration returns the duration for a shard group based on a policy duration.
|
|
func shardGroupDuration(d time.Duration) time.Duration {
|
|
if d >= 180*24*time.Hour || d == 0 { // 6 months or 0
|
|
return 7 * 24 * time.Hour
|
|
} else if d >= 2*24*time.Hour { // 2 days
|
|
return 1 * 24 * time.Hour
|
|
}
|
|
return 1 * time.Hour
|
|
}
|
|
|
|
// ShardGroupInfo represents metadata about a shard group. The DeletedAt field is important
|
|
// because it makes it clear that a ShardGroup has been marked as deleted, and allow the system
|
|
// to be sure that a ShardGroup is not simply missing. If the DeletedAt is set, the system can
|
|
// safely delete any associated shards.
|
|
type ShardGroupInfo struct {
|
|
ID uint64
|
|
StartTime time.Time
|
|
EndTime time.Time
|
|
DeletedAt time.Time
|
|
Shards []ShardInfo
|
|
}
|
|
|
|
type ShardGroupInfos []ShardGroupInfo
|
|
|
|
func (a ShardGroupInfos) Len() int { return len(a) }
|
|
func (a ShardGroupInfos) Less(i, j int) bool { return a[i].StartTime.Before(a[j].StartTime) }
|
|
func (a ShardGroupInfos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
|
|
// Contains return true if the shard group contains data for the timestamp.
|
|
func (sgi *ShardGroupInfo) Contains(timestamp time.Time) bool {
|
|
return !sgi.StartTime.After(timestamp) && sgi.EndTime.After(timestamp)
|
|
}
|
|
|
|
// Overlaps return whether the shard group contains data for the time range between min and max
|
|
func (sgi *ShardGroupInfo) Overlaps(min, max time.Time) bool {
|
|
return !sgi.StartTime.After(max) && sgi.EndTime.After(min)
|
|
}
|
|
|
|
// Deleted returns whether this ShardGroup has been deleted.
|
|
func (sgi *ShardGroupInfo) Deleted() bool {
|
|
return !sgi.DeletedAt.IsZero()
|
|
}
|
|
|
|
// clone returns a deep copy of sgi.
|
|
func (sgi ShardGroupInfo) clone() ShardGroupInfo {
|
|
other := sgi
|
|
|
|
if sgi.Shards != nil {
|
|
other.Shards = make([]ShardInfo, len(sgi.Shards))
|
|
for i := range sgi.Shards {
|
|
other.Shards[i] = sgi.Shards[i].clone()
|
|
}
|
|
}
|
|
|
|
return other
|
|
}
|
|
|
|
// ShardFor returns the ShardInfo for a Point hash
|
|
func (s *ShardGroupInfo) ShardFor(hash uint64) ShardInfo {
|
|
return s.Shards[hash%uint64(len(s.Shards))]
|
|
}
|
|
|
|
// marshal serializes to a protobuf representation.
|
|
func (sgi *ShardGroupInfo) marshal() *internal.ShardGroupInfo {
|
|
pb := &internal.ShardGroupInfo{
|
|
ID: proto.Uint64(sgi.ID),
|
|
StartTime: proto.Int64(MarshalTime(sgi.StartTime)),
|
|
EndTime: proto.Int64(MarshalTime(sgi.EndTime)),
|
|
DeletedAt: proto.Int64(MarshalTime(sgi.DeletedAt)),
|
|
}
|
|
|
|
pb.Shards = make([]*internal.ShardInfo, len(sgi.Shards))
|
|
for i := range sgi.Shards {
|
|
pb.Shards[i] = sgi.Shards[i].marshal()
|
|
}
|
|
|
|
return pb
|
|
}
|
|
|
|
// unmarshal deserializes from a protobuf representation.
|
|
func (sgi *ShardGroupInfo) unmarshal(pb *internal.ShardGroupInfo) {
|
|
sgi.ID = pb.GetID()
|
|
sgi.StartTime = UnmarshalTime(pb.GetStartTime())
|
|
sgi.EndTime = UnmarshalTime(pb.GetEndTime())
|
|
sgi.DeletedAt = UnmarshalTime(pb.GetDeletedAt())
|
|
|
|
if len(pb.GetShards()) > 0 {
|
|
sgi.Shards = make([]ShardInfo, len(pb.GetShards()))
|
|
for i, x := range pb.GetShards() {
|
|
sgi.Shards[i].unmarshal(x)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ShardInfo represents metadata about a shard.
|
|
type ShardInfo struct {
|
|
ID uint64
|
|
Owners []ShardOwner
|
|
}
|
|
|
|
// OwnedBy returns whether the shard's owner IDs includes nodeID.
|
|
func (si ShardInfo) OwnedBy(nodeID uint64) bool {
|
|
for _, so := range si.Owners {
|
|
if so.NodeID == nodeID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// clone returns a deep copy of si.
|
|
func (si ShardInfo) clone() ShardInfo {
|
|
other := si
|
|
|
|
if si.Owners != nil {
|
|
other.Owners = make([]ShardOwner, len(si.Owners))
|
|
for i := range si.Owners {
|
|
other.Owners[i] = si.Owners[i].clone()
|
|
}
|
|
}
|
|
|
|
return other
|
|
}
|
|
|
|
// marshal serializes to a protobuf representation.
|
|
func (si ShardInfo) marshal() *internal.ShardInfo {
|
|
pb := &internal.ShardInfo{
|
|
ID: proto.Uint64(si.ID),
|
|
}
|
|
|
|
pb.Owners = make([]*internal.ShardOwner, len(si.Owners))
|
|
for i := range si.Owners {
|
|
pb.Owners[i] = si.Owners[i].marshal()
|
|
}
|
|
|
|
return pb
|
|
}
|
|
|
|
// UnmarshalBinary decodes the object from a binary format.
|
|
func (si *ShardInfo) UnmarshalBinary(buf []byte) error {
|
|
var pb internal.ShardInfo
|
|
if err := proto.Unmarshal(buf, &pb); err != nil {
|
|
return err
|
|
}
|
|
si.unmarshal(&pb)
|
|
return nil
|
|
}
|
|
|
|
// unmarshal deserializes from a protobuf representation.
|
|
func (si *ShardInfo) unmarshal(pb *internal.ShardInfo) {
|
|
si.ID = pb.GetID()
|
|
|
|
// If deprecated "OwnerIDs" exists then convert it to "Owners" format.
|
|
if len(pb.GetOwnerIDs()) > 0 {
|
|
si.Owners = make([]ShardOwner, len(pb.GetOwnerIDs()))
|
|
for i, x := range pb.GetOwnerIDs() {
|
|
si.Owners[i].unmarshal(&internal.ShardOwner{
|
|
NodeID: proto.Uint64(x),
|
|
})
|
|
}
|
|
} else if len(pb.GetOwners()) > 0 {
|
|
si.Owners = make([]ShardOwner, len(pb.GetOwners()))
|
|
for i, x := range pb.GetOwners() {
|
|
si.Owners[i].unmarshal(x)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ShardOwner represents a node that owns a shard.
|
|
type ShardOwner struct {
|
|
NodeID uint64
|
|
}
|
|
|
|
// clone returns a deep copy of so.
|
|
func (so ShardOwner) clone() ShardOwner {
|
|
return so
|
|
}
|
|
|
|
// marshal serializes to a protobuf representation.
|
|
func (so ShardOwner) marshal() *internal.ShardOwner {
|
|
return &internal.ShardOwner{
|
|
NodeID: proto.Uint64(so.NodeID),
|
|
}
|
|
}
|
|
|
|
// unmarshal deserializes from a protobuf representation.
|
|
func (so *ShardOwner) unmarshal(pb *internal.ShardOwner) {
|
|
so.NodeID = pb.GetNodeID()
|
|
}
|
|
|
|
// ContinuousQueryInfo represents metadata about a continuous query.
|
|
type ContinuousQueryInfo struct {
|
|
Name string
|
|
Query string
|
|
}
|
|
|
|
// clone returns a deep copy of cqi.
|
|
func (cqi ContinuousQueryInfo) clone() ContinuousQueryInfo { return cqi }
|
|
|
|
// marshal serializes to a protobuf representation.
|
|
func (cqi ContinuousQueryInfo) marshal() *internal.ContinuousQueryInfo {
|
|
return &internal.ContinuousQueryInfo{
|
|
Name: proto.String(cqi.Name),
|
|
Query: proto.String(cqi.Query),
|
|
}
|
|
}
|
|
|
|
// unmarshal deserializes from a protobuf representation.
|
|
func (cqi *ContinuousQueryInfo) unmarshal(pb *internal.ContinuousQueryInfo) {
|
|
cqi.Name = pb.GetName()
|
|
cqi.Query = pb.GetQuery()
|
|
}
|
|
|
|
// UserInfo represents metadata about a user in the system.
|
|
type UserInfo struct {
|
|
Name string
|
|
Hash string
|
|
Admin bool
|
|
Privileges map[string]influxql.Privilege
|
|
}
|
|
|
|
// Authorize returns true if the user is authorized and false if not.
|
|
func (ui *UserInfo) Authorize(privilege influxql.Privilege, database string) bool {
|
|
if ui.Admin {
|
|
return true
|
|
}
|
|
p, ok := ui.Privileges[database]
|
|
return ok && (p == privilege || p == influxql.AllPrivileges)
|
|
}
|
|
|
|
// clone returns a deep copy of si.
|
|
func (ui UserInfo) clone() UserInfo {
|
|
other := ui
|
|
|
|
if ui.Privileges != nil {
|
|
other.Privileges = make(map[string]influxql.Privilege)
|
|
for k, v := range ui.Privileges {
|
|
other.Privileges[k] = v
|
|
}
|
|
}
|
|
|
|
return other
|
|
}
|
|
|
|
// marshal serializes to a protobuf representation.
|
|
func (ui UserInfo) marshal() *internal.UserInfo {
|
|
pb := &internal.UserInfo{
|
|
Name: proto.String(ui.Name),
|
|
Hash: proto.String(ui.Hash),
|
|
Admin: proto.Bool(ui.Admin),
|
|
}
|
|
|
|
for database, privilege := range ui.Privileges {
|
|
pb.Privileges = append(pb.Privileges, &internal.UserPrivilege{
|
|
Database: proto.String(database),
|
|
Privilege: proto.Int32(int32(privilege)),
|
|
})
|
|
}
|
|
|
|
return pb
|
|
}
|
|
|
|
// unmarshal deserializes from a protobuf representation.
|
|
func (ui *UserInfo) unmarshal(pb *internal.UserInfo) {
|
|
ui.Name = pb.GetName()
|
|
ui.Hash = pb.GetHash()
|
|
ui.Admin = pb.GetAdmin()
|
|
|
|
ui.Privileges = make(map[string]influxql.Privilege)
|
|
for _, p := range pb.GetPrivileges() {
|
|
ui.Privileges[p.GetDatabase()] = influxql.Privilege(p.GetPrivilege())
|
|
}
|
|
}
|
|
|
|
// MarshalTime converts t to nanoseconds since epoch. A zero time returns 0.
|
|
func MarshalTime(t time.Time) int64 {
|
|
if t.IsZero() {
|
|
return 0
|
|
}
|
|
return t.UnixNano()
|
|
}
|
|
|
|
// UnmarshalTime converts nanoseconds since epoch to time.
|
|
// A zero value returns a zero time.
|
|
func UnmarshalTime(v int64) time.Time {
|
|
if v == 0 {
|
|
return time.Time{}
|
|
}
|
|
return time.Unix(0, v).UTC()
|
|
}
|