1package meta_test
2
3import (
4	"fmt"
5	"math/rand"
6	"reflect"
7	"testing"
8	"time"
9
10	"github.com/influxdata/influxdb"
11	"github.com/influxdata/influxdb/pkg/testing/assert"
12	"github.com/influxdata/influxql"
13
14	"github.com/influxdata/influxdb/services/meta"
15)
16
17func init() {
18	rand.Seed(time.Now().UnixNano())
19}
20
21func Test_Data_DropDatabase(t *testing.T) {
22	data := &meta.Data{
23		Databases: []meta.DatabaseInfo{
24			{Name: "db0"},
25			{Name: "db1"},
26			{Name: "db2"},
27			{Name: "db4"},
28			{Name: "db5"},
29		},
30		Users: []meta.UserInfo{
31			{Name: "user1", Privileges: map[string]influxql.Privilege{"db1": influxql.ReadPrivilege, "db2": influxql.ReadPrivilege}},
32			{Name: "user2", Privileges: map[string]influxql.Privilege{"db2": influxql.ReadPrivilege}},
33		},
34	}
35
36	// Dropping the first database removes it from the Data object.
37	expDbs := make([]meta.DatabaseInfo, 4)
38	copy(expDbs, data.Databases[1:])
39	if err := data.DropDatabase("db0"); err != nil {
40		t.Fatal(err)
41	} else if got, exp := data.Databases, expDbs; !reflect.DeepEqual(got, exp) {
42		t.Fatalf("got %v, expected %v", got, exp)
43	}
44
45	// Dropping a middle database removes it from the data object.
46	expDbs = []meta.DatabaseInfo{{Name: "db1"}, {Name: "db2"}, {Name: "db5"}}
47	if err := data.DropDatabase("db4"); err != nil {
48		t.Fatal(err)
49	} else if got, exp := data.Databases, expDbs; !reflect.DeepEqual(got, exp) {
50		t.Fatalf("got %v, expected %v", got, exp)
51	}
52
53	// Dropping the last database removes it from the data object.
54	expDbs = []meta.DatabaseInfo{{Name: "db1"}, {Name: "db2"}}
55	if err := data.DropDatabase("db5"); err != nil {
56		t.Fatal(err)
57	} else if got, exp := data.Databases, expDbs; !reflect.DeepEqual(got, exp) {
58		t.Fatalf("got %v, expected %v", got, exp)
59	}
60
61	// Dropping a database also drops all the user privileges associated with
62	// it.
63	expUsers := []meta.UserInfo{
64		{Name: "user1", Privileges: map[string]influxql.Privilege{"db1": influxql.ReadPrivilege}},
65		{Name: "user2", Privileges: map[string]influxql.Privilege{}},
66	}
67	if err := data.DropDatabase("db2"); err != nil {
68		t.Fatal(err)
69	} else if got, exp := data.Users, expUsers; !reflect.DeepEqual(got, exp) {
70		t.Fatalf("got %v, expected %v", got, exp)
71	}
72}
73
74func Test_Data_CreateDatabase(t *testing.T) {
75	data := meta.Data{}
76
77	// Test creating a database succeedes.
78	if err := data.CreateDatabase("foo"); err != nil {
79		t.Fatal(err)
80	}
81
82	// Test creating a database with a name that is too long fails.
83	name := randString(meta.MaxNameLen + 1)
84	if err := data.CreateDatabase(name); err != meta.ErrNameTooLong {
85		t.Fatalf("exp: %v, got: %v", meta.ErrNameTooLong, err)
86	}
87}
88
89func Test_Data_CreateRetentionPolicy(t *testing.T) {
90	data := meta.Data{}
91
92	err := data.CreateDatabase("foo")
93	if err != nil {
94		t.Fatal(err)
95	}
96
97	err = data.CreateRetentionPolicy("foo", &meta.RetentionPolicyInfo{
98		Name:     "bar",
99		ReplicaN: 1,
100		Duration: 24 * time.Hour,
101	}, false)
102	if err != nil {
103		t.Fatal(err)
104	}
105
106	rp, err := data.RetentionPolicy("foo", "bar")
107	if err != nil {
108		t.Fatal(err)
109	}
110
111	if rp == nil {
112		t.Fatal("creation of retention policy failed")
113	}
114
115	// Try to recreate the same RP with default set to true, should fail
116	err = data.CreateRetentionPolicy("foo", &meta.RetentionPolicyInfo{
117		Name:     "bar",
118		ReplicaN: 1,
119		Duration: 24 * time.Hour,
120	}, true)
121	if err == nil || err != meta.ErrRetentionPolicyConflict {
122		t.Fatalf("unexpected error.  got: %v, exp: %s", err, meta.ErrRetentionPolicyConflict)
123	}
124
125	// Creating the same RP with the same specifications should succeed
126	err = data.CreateRetentionPolicy("foo", &meta.RetentionPolicyInfo{
127		Name:     "bar",
128		ReplicaN: 1,
129		Duration: 24 * time.Hour,
130	}, false)
131	if err != nil {
132		t.Fatal(err)
133	}
134
135	// Try creating a retention policy with a name that is too long. Should fail.
136	err = data.CreateRetentionPolicy("foo", &meta.RetentionPolicyInfo{
137		Name:     randString(meta.MaxNameLen + 1),
138		ReplicaN: 1,
139		Duration: 24 * time.Hour,
140	}, true)
141	if err != meta.ErrNameTooLong {
142		t.Fatalf("exp: %v, got %v", meta.ErrNameTooLong, err)
143	}
144}
145
146func TestData_AdminUserExists(t *testing.T) {
147	data := meta.Data{}
148
149	// No users means no admin.
150	if data.AdminUserExists() {
151		t.Fatal("no admin user should exist")
152	}
153
154	// Add a non-admin user.
155	if err := data.CreateUser("user1", "a", false); err != nil {
156		t.Fatal(err)
157	}
158	if got, exp := data.AdminUserExists(), false; got != exp {
159		t.Fatalf("got %v, expected %v", got, exp)
160	}
161
162	// Add an admin user.
163	if err := data.CreateUser("admin1", "a", true); err != nil {
164		t.Fatal(err)
165	}
166	if got, exp := data.AdminUserExists(), true; got != exp {
167		t.Fatalf("got %v, expected %v", got, exp)
168	}
169
170	// Remove the original user
171	if err := data.DropUser("user1"); err != nil {
172		t.Fatal(err)
173	}
174	if got, exp := data.AdminUserExists(), true; got != exp {
175		t.Fatalf("got %v, expected %v", got, exp)
176	}
177
178	// Add another admin
179	if err := data.CreateUser("admin2", "a", true); err != nil {
180		t.Fatal(err)
181	}
182	if got, exp := data.AdminUserExists(), true; got != exp {
183		t.Fatalf("got %v, expected %v", got, exp)
184	}
185
186	// Revoke privileges of the first admin
187	if err := data.SetAdminPrivilege("admin1", false); err != nil {
188		t.Fatal(err)
189	}
190	if got, exp := data.AdminUserExists(), true; got != exp {
191		t.Fatalf("got %v, expected %v", got, exp)
192	}
193
194	// Add user1 back.
195	if err := data.CreateUser("user1", "a", false); err != nil {
196		t.Fatal(err)
197	}
198	// Revoke remaining admin.
199	if err := data.SetAdminPrivilege("admin2", false); err != nil {
200		t.Fatal(err)
201	}
202	// No longer any admins
203	if got, exp := data.AdminUserExists(), false; got != exp {
204		t.Fatalf("got %v, expected %v", got, exp)
205	}
206
207	// Make user1 an admin
208	if err := data.SetAdminPrivilege("user1", true); err != nil {
209		t.Fatal(err)
210	}
211	if got, exp := data.AdminUserExists(), true; got != exp {
212		t.Fatalf("got %v, expected %v", got, exp)
213	}
214
215	// Drop user1...
216	if err := data.DropUser("user1"); err != nil {
217		t.Fatal(err)
218	}
219	if got, exp := data.AdminUserExists(), false; got != exp {
220		t.Fatalf("got %v, expected %v", got, exp)
221	}
222}
223
224func TestData_SetPrivilege(t *testing.T) {
225	data := meta.Data{}
226	if err := data.CreateDatabase("db0"); err != nil {
227		t.Fatal(err)
228	}
229
230	if err := data.CreateUser("user1", "", false); err != nil {
231		t.Fatal(err)
232	}
233
234	// When the user does not exist, SetPrivilege returns an error.
235	if got, exp := data.SetPrivilege("not a user", "db0", influxql.AllPrivileges), meta.ErrUserNotFound; got != exp {
236		t.Fatalf("got %v, expected %v", got, exp)
237	}
238
239	// When the database does not exist, SetPrivilege returns an error.
240	if got, exp := data.SetPrivilege("user1", "db1", influxql.AllPrivileges), influxdb.ErrDatabaseNotFound("db1"); got == nil || got.Error() != exp.Error() {
241		t.Fatalf("got %v, expected %v", got, exp)
242	}
243
244	// Otherwise, SetPrivilege sets the expected privileges.
245	if got := data.SetPrivilege("user1", "db0", influxql.AllPrivileges); got != nil {
246		t.Fatalf("got %v, expected %v", got, nil)
247	}
248}
249
250func TestData_TruncateShardGroups(t *testing.T) {
251	data := &meta.Data{}
252
253	must := func(err error) {
254		if err != nil {
255			t.Fatal(err)
256		}
257	}
258
259	must(data.CreateDatabase("db"))
260	rp := meta.NewRetentionPolicyInfo("rp")
261	rp.ShardGroupDuration = 24 * time.Hour
262	must(data.CreateRetentionPolicy("db", rp, true))
263
264	must(data.CreateShardGroup("db", "rp", time.Unix(0, 0)))
265
266	sg0, err := data.ShardGroupByTimestamp("db", "rp", time.Unix(0, 0))
267	if err != nil {
268		t.Fatal("Failed to find shard group:", err)
269	}
270
271	if sg0.Truncated() {
272		t.Fatal("shard group already truncated")
273	}
274
275	sgEnd, err := data.ShardGroupByTimestamp("db", "rp", sg0.StartTime.Add(rp.ShardGroupDuration-1))
276	if err != nil {
277		t.Fatal("Failed to find shard group for end range:", err)
278	}
279
280	if sgEnd == nil || sgEnd.ID != sg0.ID {
281		t.Fatalf("Retention policy mis-match: Expected %v, Got %v", sg0, sgEnd)
282	}
283
284	must(data.CreateShardGroup("db", "rp", sg0.StartTime.Add(rp.ShardGroupDuration)))
285
286	sg1, err := data.ShardGroupByTimestamp("db", "rp", sg0.StartTime.Add(rp.ShardGroupDuration+time.Minute))
287	if err != nil {
288		t.Fatal("Failed to find second shard group:", err)
289	}
290
291	if sg1.Truncated() {
292		t.Fatal("second shard group already truncated")
293	}
294
295	// shouldn't do anything
296	must(data.CreateShardGroup("db", "rp", sg0.EndTime.Add(-time.Minute)))
297
298	sgs, err := data.ShardGroupsByTimeRange("db", "rp", time.Unix(0, 0), sg1.EndTime.Add(time.Minute))
299	if err != nil {
300		t.Fatal("Failed to find shard groups:", err)
301	}
302
303	if len(sgs) != 2 {
304		t.Fatalf("Expected %d shard groups, found %d", 2, len(sgs))
305	}
306
307	truncateTime := sg0.EndTime.Add(-time.Minute)
308	data.TruncateShardGroups(truncateTime)
309
310	// at this point, we should get nil shard groups for times after truncateTime
311	for _, tc := range []struct {
312		t      time.Time
313		exists bool
314	}{
315		{sg0.StartTime, true},
316		{sg0.EndTime.Add(-1), false},
317		{truncateTime.Add(-1), true},
318		{truncateTime, false},
319		{sg1.StartTime, false},
320	} {
321		sg, err := data.ShardGroupByTimestamp("db", "rp", tc.t)
322		if err != nil {
323			t.Fatalf("Failed to find shardgroup for %v: %v", tc.t, err)
324		}
325		if tc.exists && sg == nil {
326			t.Fatalf("Shard group for timestamp '%v' should exist, got nil", tc.t)
327		}
328	}
329
330	for _, x := range data.Databases[0].RetentionPolicies[0].ShardGroups {
331		switch x.ID {
332		case sg0.ID:
333			*sg0 = x
334		case sg1.ID:
335			*sg1 = x
336		}
337	}
338
339	if sg0.TruncatedAt != truncateTime {
340		t.Fatalf("Incorrect truncation of current shard group. Expected %v, got %v", truncateTime, sg0.TruncatedAt)
341	}
342
343	if sg1.TruncatedAt != sg1.StartTime {
344		t.Fatalf("Incorrect truncation of future shard group. Expected %v, got %v", sg1.StartTime, sg1.TruncatedAt)
345	}
346}
347
348func TestUserInfo_AuthorizeDatabase(t *testing.T) {
349	emptyUser := &meta.UserInfo{}
350	if !emptyUser.AuthorizeDatabase(influxql.NoPrivileges, "anydb") {
351		t.Fatal("expected NoPrivileges to be authorized but it wasn't")
352	}
353	if emptyUser.AuthorizeDatabase(influxql.ReadPrivilege, "anydb") {
354		t.Fatal("expected ReadPrivilege to prevent authorization, but it was authorized")
355	}
356
357	adminUser := &meta.UserInfo{Admin: true}
358	if !adminUser.AuthorizeDatabase(influxql.AllPrivileges, "anydb") {
359		t.Fatalf("expected admin to be authorized but it wasn't")
360	}
361}
362
363func TestShardGroupInfo_Contains(t *testing.T) {
364	sgi := &meta.ShardGroupInfo{StartTime: time.Unix(10, 0), EndTime: time.Unix(20, 0)}
365
366	tests := []struct {
367		ts  time.Time
368		exp bool
369	}{
370		{time.Unix(0, 0), false},
371		{time.Unix(9, 0), false},
372		{time.Unix(10, 0), true},
373		{time.Unix(11, 0), true},
374		{time.Unix(15, 0), true},
375		{time.Unix(19, 0), true},
376		{time.Unix(20, 0), false},
377		{time.Unix(21, 0), false},
378	}
379	for _, test := range tests {
380		t.Run(fmt.Sprintf("ts=%d", test.ts.Unix()), func(t *testing.T) {
381			got := sgi.Contains(test.ts)
382			assert.Equal(t, got, test.exp)
383		})
384	}
385}
386
387func randString(n int) string {
388	var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
389	b := make([]rune, n)
390	for i := range b {
391		b[i] = letters[rand.Intn(len(letters))]
392	}
393	return string(b)
394}
395