476 lines
14 KiB
Go
476 lines
14 KiB
Go
package meta_test
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/influxdata/influxdb/v2/pkg/testing/assert"
|
|
influxdb "github.com/influxdata/influxdb/v2/v1"
|
|
"github.com/influxdata/influxdb/v2/v1/services/meta"
|
|
"github.com/influxdata/influxql"
|
|
)
|
|
|
|
func init() {
|
|
rand.Seed(time.Now().UnixNano())
|
|
}
|
|
|
|
func Test_Data_DropDatabase(t *testing.T) {
|
|
data := &meta.Data{
|
|
Databases: []meta.DatabaseInfo{
|
|
{Name: "db0"},
|
|
{Name: "db1"},
|
|
{Name: "db2"},
|
|
{Name: "db4"},
|
|
{Name: "db5"},
|
|
},
|
|
Users: []meta.UserInfo{
|
|
{Name: "user1", Privileges: map[string]influxql.Privilege{"db1": influxql.ReadPrivilege, "db2": influxql.ReadPrivilege}},
|
|
{Name: "user2", Privileges: map[string]influxql.Privilege{"db2": influxql.ReadPrivilege}},
|
|
},
|
|
}
|
|
|
|
// Dropping the first database removes it from the Data object.
|
|
expDbs := make([]meta.DatabaseInfo, 4)
|
|
copy(expDbs, data.Databases[1:])
|
|
if err := data.DropDatabase("db0"); err != nil {
|
|
t.Fatal(err)
|
|
} else if got, exp := data.Databases, expDbs; !reflect.DeepEqual(got, exp) {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
|
|
// Dropping a middle database removes it from the data object.
|
|
expDbs = []meta.DatabaseInfo{{Name: "db1"}, {Name: "db2"}, {Name: "db5"}}
|
|
if err := data.DropDatabase("db4"); err != nil {
|
|
t.Fatal(err)
|
|
} else if got, exp := data.Databases, expDbs; !reflect.DeepEqual(got, exp) {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
|
|
// Dropping the last database removes it from the data object.
|
|
expDbs = []meta.DatabaseInfo{{Name: "db1"}, {Name: "db2"}}
|
|
if err := data.DropDatabase("db5"); err != nil {
|
|
t.Fatal(err)
|
|
} else if got, exp := data.Databases, expDbs; !reflect.DeepEqual(got, exp) {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
|
|
// Dropping a database also drops all the user privileges associated with
|
|
// it.
|
|
expUsers := []meta.UserInfo{
|
|
{Name: "user1", Privileges: map[string]influxql.Privilege{"db1": influxql.ReadPrivilege}},
|
|
{Name: "user2", Privileges: map[string]influxql.Privilege{}},
|
|
}
|
|
if err := data.DropDatabase("db2"); err != nil {
|
|
t.Fatal(err)
|
|
} else if got, exp := data.Users, expUsers; !reflect.DeepEqual(got, exp) {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
}
|
|
|
|
func Test_Data_CreateDatabase(t *testing.T) {
|
|
data := meta.Data{}
|
|
|
|
// Test creating a database succeeds.
|
|
if err := data.CreateDatabase("foo"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Test creating a database with a name that is too long fails.
|
|
name := randString(meta.MaxNameLen + 1)
|
|
if err := data.CreateDatabase(name); err != meta.ErrNameTooLong {
|
|
t.Fatalf("exp: %v, got: %v", meta.ErrNameTooLong, err)
|
|
}
|
|
}
|
|
|
|
func Test_Data_CreateRetentionPolicy(t *testing.T) {
|
|
data := meta.Data{}
|
|
|
|
err := data.CreateDatabase("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = data.CreateRetentionPolicy("foo", &meta.RetentionPolicyInfo{
|
|
Name: "bar",
|
|
ReplicaN: 1,
|
|
Duration: 24 * time.Hour,
|
|
}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rp, err := data.RetentionPolicy("foo", "bar")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if rp == nil {
|
|
t.Fatal("creation of retention policy failed")
|
|
}
|
|
|
|
// Try to recreate the same RP with default set to true, should fail
|
|
err = data.CreateRetentionPolicy("foo", &meta.RetentionPolicyInfo{
|
|
Name: "bar",
|
|
ReplicaN: 1,
|
|
Duration: 24 * time.Hour,
|
|
}, true)
|
|
if err == nil || err != meta.ErrRetentionPolicyConflict {
|
|
t.Fatalf("unexpected error. got: %v, exp: %s", err, meta.ErrRetentionPolicyConflict)
|
|
}
|
|
|
|
// Creating the same RP with the same specifications should succeed
|
|
err = data.CreateRetentionPolicy("foo", &meta.RetentionPolicyInfo{
|
|
Name: "bar",
|
|
ReplicaN: 1,
|
|
Duration: 24 * time.Hour,
|
|
}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Try creating a retention policy with a name that is too long. Should fail.
|
|
err = data.CreateRetentionPolicy("foo", &meta.RetentionPolicyInfo{
|
|
Name: randString(meta.MaxNameLen + 1),
|
|
ReplicaN: 1,
|
|
Duration: 24 * time.Hour,
|
|
}, true)
|
|
if err != meta.ErrNameTooLong {
|
|
t.Fatalf("exp: %v, got %v", meta.ErrNameTooLong, err)
|
|
}
|
|
}
|
|
|
|
func TestData_AdminUserExists(t *testing.T) {
|
|
data := meta.Data{}
|
|
|
|
// No users means no admin.
|
|
if data.AdminUserExists() {
|
|
t.Fatal("no admin user should exist")
|
|
}
|
|
|
|
// Add a non-admin user.
|
|
if err := data.CreateUser("user1", "a", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, exp := data.AdminUserExists(), false; got != exp {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
|
|
// Add an admin user.
|
|
if err := data.CreateUser("admin1", "a", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, exp := data.AdminUserExists(), true; got != exp {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
|
|
// Remove the original user
|
|
if err := data.DropUser("user1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, exp := data.AdminUserExists(), true; got != exp {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
|
|
// Add another admin
|
|
if err := data.CreateUser("admin2", "a", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, exp := data.AdminUserExists(), true; got != exp {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
|
|
// Revoke privileges of the first admin
|
|
if err := data.SetAdminPrivilege("admin1", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, exp := data.AdminUserExists(), true; got != exp {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
|
|
// Add user1 back.
|
|
if err := data.CreateUser("user1", "a", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Revoke remaining admin.
|
|
if err := data.SetAdminPrivilege("admin2", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// No longer any admins
|
|
if got, exp := data.AdminUserExists(), false; got != exp {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
|
|
// Make user1 an admin
|
|
if err := data.SetAdminPrivilege("user1", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, exp := data.AdminUserExists(), true; got != exp {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
|
|
// Drop user1...
|
|
if err := data.DropUser("user1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, exp := data.AdminUserExists(), false; got != exp {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
}
|
|
|
|
func TestData_SetPrivilege(t *testing.T) {
|
|
data := meta.Data{}
|
|
if err := data.CreateDatabase("db0"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := data.CreateUser("user1", "", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// When the user does not exist, SetPrivilege returns an error.
|
|
if got, exp := data.SetPrivilege("not a user", "db0", influxql.AllPrivileges), meta.ErrUserNotFound; got != exp {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
|
|
// When the database does not exist, SetPrivilege returns an error.
|
|
if got, exp := data.SetPrivilege("user1", "db1", influxql.AllPrivileges), influxdb.ErrDatabaseNotFound("db1"); got == nil || got.Error() != exp.Error() {
|
|
t.Fatalf("got %v, expected %v", got, exp)
|
|
}
|
|
|
|
// Otherwise, SetPrivilege sets the expected privileges.
|
|
if got := data.SetPrivilege("user1", "db0", influxql.AllPrivileges); got != nil {
|
|
t.Fatalf("got %v, expected %v", got, nil)
|
|
}
|
|
}
|
|
|
|
func TestData_TruncateShardGroups(t *testing.T) {
|
|
data := &meta.Data{}
|
|
|
|
must := func(err error) {
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
must(data.CreateDatabase("db"))
|
|
rp := meta.NewRetentionPolicyInfo("rp")
|
|
rp.ShardGroupDuration = 24 * time.Hour
|
|
must(data.CreateRetentionPolicy("db", rp, true))
|
|
|
|
must(data.CreateShardGroup("db", "rp", time.Unix(0, 0)))
|
|
|
|
sg0, err := data.ShardGroupByTimestamp("db", "rp", time.Unix(0, 0))
|
|
if err != nil {
|
|
t.Fatal("Failed to find shard group:", err)
|
|
}
|
|
|
|
if sg0.Truncated() {
|
|
t.Fatal("shard group already truncated")
|
|
}
|
|
|
|
sgEnd, err := data.ShardGroupByTimestamp("db", "rp", sg0.StartTime.Add(rp.ShardGroupDuration-1))
|
|
if err != nil {
|
|
t.Fatal("Failed to find shard group for end range:", err)
|
|
}
|
|
|
|
if sgEnd == nil || sgEnd.ID != sg0.ID {
|
|
t.Fatalf("Retention policy mis-match: Expected %v, Got %v", sg0, sgEnd)
|
|
}
|
|
|
|
must(data.CreateShardGroup("db", "rp", sg0.StartTime.Add(rp.ShardGroupDuration)))
|
|
|
|
sg1, err := data.ShardGroupByTimestamp("db", "rp", sg0.StartTime.Add(rp.ShardGroupDuration+time.Minute))
|
|
if err != nil {
|
|
t.Fatal("Failed to find second shard group:", err)
|
|
}
|
|
|
|
if sg1.Truncated() {
|
|
t.Fatal("second shard group already truncated")
|
|
}
|
|
|
|
// shouldn't do anything
|
|
must(data.CreateShardGroup("db", "rp", sg0.EndTime.Add(-time.Minute)))
|
|
|
|
sgs, err := data.ShardGroupsByTimeRange("db", "rp", time.Unix(0, 0), sg1.EndTime.Add(time.Minute))
|
|
if err != nil {
|
|
t.Fatal("Failed to find shard groups:", err)
|
|
}
|
|
|
|
if len(sgs) != 2 {
|
|
t.Fatalf("Expected %d shard groups, found %d", 2, len(sgs))
|
|
}
|
|
|
|
truncateTime := sg0.EndTime.Add(-time.Minute)
|
|
data.TruncateShardGroups(truncateTime)
|
|
|
|
// at this point, we should get nil shard groups for times after truncateTime
|
|
for _, tc := range []struct {
|
|
t time.Time
|
|
exists bool
|
|
}{
|
|
{sg0.StartTime, true},
|
|
{sg0.EndTime.Add(-1), false},
|
|
{truncateTime.Add(-1), true},
|
|
{truncateTime, false},
|
|
{sg1.StartTime, false},
|
|
} {
|
|
sg, err := data.ShardGroupByTimestamp("db", "rp", tc.t)
|
|
if err != nil {
|
|
t.Fatalf("Failed to find shardgroup for %v: %v", tc.t, err)
|
|
}
|
|
if tc.exists && sg == nil {
|
|
t.Fatalf("Shard group for timestamp '%v' should exist, got nil", tc.t)
|
|
}
|
|
}
|
|
|
|
for _, x := range data.Databases[0].RetentionPolicies[0].ShardGroups {
|
|
switch x.ID {
|
|
case sg0.ID:
|
|
*sg0 = x
|
|
case sg1.ID:
|
|
*sg1 = x
|
|
}
|
|
}
|
|
|
|
if sg0.TruncatedAt != truncateTime {
|
|
t.Fatalf("Incorrect truncation of current shard group. Expected %v, got %v", truncateTime, sg0.TruncatedAt)
|
|
}
|
|
|
|
if sg1.TruncatedAt != sg1.StartTime {
|
|
t.Fatalf("Incorrect truncation of future shard group. Expected %v, got %v", sg1.StartTime, sg1.TruncatedAt)
|
|
}
|
|
|
|
groups := data.Databases[0].RetentionPolicies[0].ShardGroups
|
|
assert.Equal(t, 2, len(groups))
|
|
assert.Equal(t, "1970-01-01 00:00:00 +0000 UTC", groups[0].StartTime.String())
|
|
assert.Equal(t, "1970-01-02 00:00:00 +0000 UTC", groups[0].EndTime.String())
|
|
assert.Equal(t, "1970-01-01 23:59:00 +0000 UTC", groups[0].TruncatedAt.String())
|
|
|
|
assert.Equal(t, "1970-01-02 00:00:00 +0000 UTC", groups[1].StartTime.String())
|
|
assert.Equal(t, "1970-01-03 00:00:00 +0000 UTC", groups[1].EndTime.String())
|
|
assert.Equal(t, "1970-01-02 00:00:00 +0000 UTC", groups[1].TruncatedAt.String())
|
|
|
|
// Create some more shard groups and validate there is no overlap
|
|
// Add a shard starting at sg0's truncation time, until 01/02
|
|
must(data.CreateShardGroup("db", "rp", sg0.EndTime.Add(-time.Second)))
|
|
// Add a shard 01/02 - 01/03 (since sg1 is fully truncated)
|
|
must(data.CreateShardGroup("db", "rp", sg1.EndTime.Add(-time.Second)))
|
|
// Add a shard 01/06 - 01/07
|
|
must(data.CreateShardGroup("db", "rp", sg1.EndTime.Add(3*rp.ShardGroupDuration)))
|
|
newDuration := 10 * rp.ShardGroupDuration
|
|
data.UpdateRetentionPolicy("db", "rp", &meta.RetentionPolicyUpdate{
|
|
Name: nil,
|
|
Duration: nil,
|
|
ReplicaN: nil,
|
|
ShardGroupDuration: &newDuration,
|
|
}, true)
|
|
// Add a shard 01/03 - 01/06
|
|
must(data.CreateShardGroup("db", "rp", sg1.EndTime.Add(1*rp.ShardGroupDuration)))
|
|
// Add a shard 01/07 - 01/09
|
|
must(data.CreateShardGroup("db", "rp", sg1.EndTime.Add(4*rp.ShardGroupDuration)))
|
|
// Add a shard 01/09 - 01/19
|
|
must(data.CreateShardGroup("db", "rp", sg1.EndTime.Add(10*rp.ShardGroupDuration)))
|
|
// No additional shard added
|
|
must(data.CreateShardGroup("db", "rp", sg1.EndTime.Add(11*rp.ShardGroupDuration)))
|
|
|
|
groups = data.Databases[0].RetentionPolicies[0].ShardGroups
|
|
assert.Equal(t, 8, len(groups))
|
|
|
|
expectTimes := []struct {
|
|
start, end, truncated string
|
|
}{
|
|
{"1970-01-01 00:00:00 +0000 UTC", "1970-01-02 00:00:00 +0000 UTC", "1970-01-01 23:59:00 +0000 UTC"},
|
|
{"1970-01-01 23:59:00 +0000 UTC", "1970-01-02 00:00:00 +0000 UTC", "0001-01-01 00:00:00 +0000 UTC"},
|
|
{"1970-01-02 00:00:00 +0000 UTC", "1970-01-03 00:00:00 +0000 UTC", "1970-01-02 00:00:00 +0000 UTC"},
|
|
{"1970-01-02 00:00:00 +0000 UTC", "1970-01-03 00:00:00 +0000 UTC", "0001-01-01 00:00:00 +0000 UTC"},
|
|
{"1970-01-03 00:00:00 +0000 UTC", "1970-01-06 00:00:00 +0000 UTC", "0001-01-01 00:00:00 +0000 UTC"},
|
|
{"1970-01-06 00:00:00 +0000 UTC", "1970-01-07 00:00:00 +0000 UTC", "0001-01-01 00:00:00 +0000 UTC"},
|
|
{"1970-01-07 00:00:00 +0000 UTC", "1970-01-09 00:00:00 +0000 UTC", "0001-01-01 00:00:00 +0000 UTC"},
|
|
{"1970-01-09 00:00:00 +0000 UTC", "1970-01-19 00:00:00 +0000 UTC", "0001-01-01 00:00:00 +0000 UTC"},
|
|
}
|
|
|
|
for i := range expectTimes {
|
|
assert.Equal(t, expectTimes[i].start, groups[i].StartTime.String(), "start time %d", i)
|
|
assert.Equal(t, expectTimes[i].end, groups[i].EndTime.String(), "end time %d", i)
|
|
assert.Equal(t, expectTimes[i].truncated, groups[i].TruncatedAt.String(), "truncate time %d", i)
|
|
}
|
|
}
|
|
|
|
func TestUserInfo_AuthorizeDatabase(t *testing.T) {
|
|
emptyUser := &meta.UserInfo{}
|
|
if !emptyUser.AuthorizeDatabase(influxql.NoPrivileges, "anydb") {
|
|
t.Fatal("expected NoPrivileges to be authorized but it wasn't")
|
|
}
|
|
if emptyUser.AuthorizeDatabase(influxql.ReadPrivilege, "anydb") {
|
|
t.Fatal("expected ReadPrivilege to prevent authorization, but it was authorized")
|
|
}
|
|
|
|
adminUser := &meta.UserInfo{Admin: true}
|
|
if !adminUser.AuthorizeDatabase(influxql.AllPrivileges, "anydb") {
|
|
t.Fatalf("expected admin to be authorized but it wasn't")
|
|
}
|
|
}
|
|
|
|
func TestShardGroupInfo_Contains(t *testing.T) {
|
|
sgi := &meta.ShardGroupInfo{StartTime: time.Unix(10, 0), EndTime: time.Unix(20, 0)}
|
|
|
|
tests := []struct {
|
|
ts time.Time
|
|
exp bool
|
|
}{
|
|
{time.Unix(0, 0), false},
|
|
{time.Unix(9, 0), false},
|
|
{time.Unix(10, 0), true},
|
|
{time.Unix(11, 0), true},
|
|
{time.Unix(15, 0), true},
|
|
{time.Unix(19, 0), true},
|
|
{time.Unix(20, 0), false},
|
|
{time.Unix(21, 0), false},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("ts=%d", test.ts.Unix()), func(t *testing.T) {
|
|
got := sgi.Contains(test.ts)
|
|
assert.Equal(t, got, test.exp)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRetentionPolicyInfo_ToSpec(t *testing.T) {
|
|
rp := &meta.RetentionPolicyInfo{
|
|
Name: "bar",
|
|
ReplicaN: 1,
|
|
Duration: 24 * time.Hour,
|
|
ShardGroupDuration: time.Hour,
|
|
}
|
|
spec := rp.ToSpec()
|
|
|
|
if spec == nil {
|
|
t.Fatal("invalid spec")
|
|
} else if spec.Name != rp.Name {
|
|
t.Fatalf("invalid name: %s", spec.Name)
|
|
} else if spec.ReplicaN == nil {
|
|
t.Fatalf("invalid ReplicaN")
|
|
} else if *spec.ReplicaN != rp.ReplicaN {
|
|
t.Fatalf("invalid ReplicaN: %d", *spec.ReplicaN)
|
|
} else if spec.Duration == nil {
|
|
t.Fatalf("invalid Duration")
|
|
} else if *spec.Duration != rp.Duration {
|
|
t.Fatalf("invalid Duration: %s", spec.Duration.String())
|
|
} else if spec.ShardGroupDuration != rp.ShardGroupDuration {
|
|
t.Fatalf("invalid ShardGroupDuration: %s", spec.ShardGroupDuration.String())
|
|
}
|
|
|
|
}
|
|
|
|
func randString(n int) string {
|
|
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
b := make([]rune, n)
|
|
for i := range b {
|
|
b[i] = letters[rand.Intn(len(letters))]
|
|
}
|
|
return string(b)
|
|
}
|