1// Copyright (C) 2020 Storj Labs, Inc.
2// See LICENSE for copying information.
3
4package metabase_test
5
6import (
7	"strconv"
8	"testing"
9
10	"github.com/stretchr/testify/require"
11
12	"storj.io/common/testrand"
13	"storj.io/common/uuid"
14	"storj.io/storj/satellite/metabase"
15)
16
17func TestParseBucketPrefixInvalid(t *testing.T) {
18	var testCases = []struct {
19		name   string
20		prefix metabase.BucketPrefix
21	}{
22		{"invalid, not valid UUID", "not UUID string/bucket1"},
23		{"invalid, not valid UUID, no bucket", "not UUID string"},
24		{"invalid, no project, no bucket", ""},
25	}
26	for _, tt := range testCases {
27		tt := tt
28		t.Run(tt.name, func(t *testing.T) {
29			_, err := metabase.ParseBucketPrefix(tt.prefix)
30			require.NotNil(t, err)
31			require.Error(t, err)
32		})
33	}
34}
35
36func TestParseBucketPrefixValid(t *testing.T) {
37	var testCases = []struct {
38		name               string
39		project            string
40		bucketName         string
41		expectedBucketName string
42	}{
43		{"valid, no bucket, no objects", "bb6218e3-4b4a-4819-abbb-fa68538e33c0", "", ""},
44		{"valid, with bucket", "bb6218e3-4b4a-4819-abbb-fa68538e33c0", "testbucket", "testbucket"},
45	}
46	for _, tt := range testCases {
47		tt := tt
48		t.Run(tt.name, func(t *testing.T) {
49			expectedProjectID, err := uuid.FromString(tt.project)
50			require.NoError(t, err)
51			bucketID := expectedProjectID.String() + "/" + tt.bucketName
52
53			bucketLocation, err := metabase.ParseBucketPrefix(metabase.BucketPrefix(bucketID))
54			require.NoError(t, err)
55			require.Equal(t, expectedProjectID, bucketLocation.ProjectID)
56			require.Equal(t, tt.expectedBucketName, bucketLocation.BucketName)
57		})
58	}
59}
60
61func TestParseSegmentKeyInvalid(t *testing.T) {
62	var testCases = []struct {
63		name       string
64		segmentKey string
65	}{
66		{
67			name:       "invalid, project ID only",
68			segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0",
69		},
70		{
71			name:       "invalid, project ID and segment index only",
72			segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0/s0",
73		},
74		{
75			name:       "invalid, project ID, bucket, and segment index only",
76			segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0/s0/testbucket",
77		},
78		{
79			name:       "invalid, project ID is not UUID",
80			segmentKey: "not UUID string/s0/testbucket/test/object",
81		},
82		{
83			name:       "invalid, last segment with segment number",
84			segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0/l0/testbucket/test/object",
85		},
86		{
87			name:       "invalid, missing segment number",
88			segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0/s/testbucket/test/object",
89		},
90		{
91			name:       "invalid, missing segment prefix",
92			segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0/1/testbucket/test/object",
93		},
94		{
95			name:       "invalid, segment index overflows int64",
96			segmentKey: "bb6218e3-4b4a-4819-abbb-fa68538e33c0/s18446744073709551616/testbucket/test/object",
97		},
98	}
99	for _, tt := range testCases {
100		tt := tt
101		t.Run(tt.name, func(t *testing.T) {
102			_, err := metabase.ParseSegmentKey(metabase.SegmentKey(tt.segmentKey))
103			require.NotNil(t, err, tt.name)
104			require.Error(t, err, tt.name)
105		})
106	}
107}
108
109func TestParseSegmentKeyValid(t *testing.T) {
110	projectID := testrand.UUID()
111
112	var testCases = []struct {
113		name             string
114		segmentKey       string
115		expectedLocation metabase.SegmentLocation
116	}{
117		{
118			name:       "valid, part 0, last segment",
119			segmentKey: projectID.String() + "/l/testbucket/test/object",
120			expectedLocation: metabase.SegmentLocation{
121				ProjectID:  projectID,
122				BucketName: "testbucket",
123				ObjectKey:  "test/object",
124				Position:   metabase.SegmentPosition{Part: 0, Index: metabase.LastSegmentIndex},
125			},
126		},
127		{
128			name:       "valid, part 0, last segment, trailing slash",
129			segmentKey: projectID.String() + "/l/testbucket/test/object/",
130			expectedLocation: metabase.SegmentLocation{
131				ProjectID:  projectID,
132				BucketName: "testbucket",
133				ObjectKey:  "test/object/",
134				Position:   metabase.SegmentPosition{Part: 0, Index: metabase.LastSegmentIndex},
135			},
136		},
137		{
138			name:       "valid, part 0, index 0",
139			segmentKey: projectID.String() + "/s0/testbucket/test/object",
140			expectedLocation: metabase.SegmentLocation{
141				ProjectID:  projectID,
142				BucketName: "testbucket",
143				ObjectKey:  "test/object",
144				Position:   metabase.SegmentPosition{Part: 0, Index: 0},
145			},
146		},
147		{
148			name:       "valid, part 0, index 1",
149			segmentKey: projectID.String() + "/s1/testbucket/test/object",
150			expectedLocation: metabase.SegmentLocation{
151				ProjectID:  projectID,
152				BucketName: "testbucket",
153				ObjectKey:  "test/object",
154				Position:   metabase.SegmentPosition{Part: 0, Index: 1},
155			},
156		},
157		{
158			name:       "valid, part 0, index 315",
159			segmentKey: projectID.String() + "/s315/testbucket/test/object",
160			expectedLocation: metabase.SegmentLocation{
161				ProjectID:  projectID,
162				BucketName: "testbucket",
163				ObjectKey:  "test/object",
164				Position:   metabase.SegmentPosition{Part: 0, Index: 315},
165			},
166		},
167		{
168			name:       "valid, part 1, index 0",
169			segmentKey: projectID.String() + "/s" + strconv.FormatInt(1<<32, 10) + "/testbucket/test/object",
170			expectedLocation: metabase.SegmentLocation{
171				ProjectID:  projectID,
172				BucketName: "testbucket",
173				ObjectKey:  "test/object",
174				Position:   metabase.SegmentPosition{Part: 1, Index: 0},
175			},
176		},
177		{
178			name:       "valid, part 1, index 1",
179			segmentKey: projectID.String() + "/s" + strconv.FormatInt(1<<32+1, 10) + "/testbucket/test/object",
180			expectedLocation: metabase.SegmentLocation{
181				ProjectID:  projectID,
182				BucketName: "testbucket",
183				ObjectKey:  "test/object",
184				Position:   metabase.SegmentPosition{Part: 1, Index: 1},
185			},
186		},
187		{
188			name:       "valid, part 18, index 315",
189			segmentKey: projectID.String() + "/s" + strconv.FormatInt(18<<32+315, 10) + "/testbucket/test/object",
190			expectedLocation: metabase.SegmentLocation{
191				ProjectID:  projectID,
192				BucketName: "testbucket",
193				ObjectKey:  "test/object",
194				Position:   metabase.SegmentPosition{Part: 18, Index: 315},
195			},
196		},
197	}
198	for _, tt := range testCases {
199		tt := tt
200		t.Run(tt.name, func(t *testing.T) {
201			segmentLocation, err := metabase.ParseSegmentKey(metabase.SegmentKey(tt.segmentKey))
202			require.NoError(t, err, tt.name)
203			require.Equal(t, tt.expectedLocation, segmentLocation)
204		})
205	}
206}
207
208func TestPiecesEqual(t *testing.T) {
209	sn1 := testrand.NodeID()
210	sn2 := testrand.NodeID()
211
212	var testCases = []struct {
213		source metabase.Pieces
214		target metabase.Pieces
215		equal  bool
216	}{
217		{metabase.Pieces{}, metabase.Pieces{}, true},
218		{
219			metabase.Pieces{
220				{1, sn1},
221			},
222			metabase.Pieces{}, false,
223		},
224		{
225			metabase.Pieces{},
226			metabase.Pieces{
227				{1, sn1},
228			}, false,
229		},
230		{
231			metabase.Pieces{
232				{1, sn1},
233				{2, sn2},
234			},
235			metabase.Pieces{
236				{1, sn1},
237				{2, sn2},
238			}, true,
239		},
240		{
241			metabase.Pieces{
242				{2, sn2},
243				{1, sn1},
244			},
245			metabase.Pieces{
246				{1, sn1},
247				{2, sn2},
248			}, true,
249		},
250		{
251			metabase.Pieces{
252				{1, sn1},
253				{2, sn2},
254			},
255			metabase.Pieces{
256				{1, sn2},
257				{2, sn1},
258			}, false,
259		},
260		{
261			metabase.Pieces{
262				{1, sn1},
263				{3, sn2},
264				{2, sn2},
265			},
266			metabase.Pieces{
267				{3, sn2},
268				{1, sn1},
269				{2, sn2},
270			}, true,
271		},
272	}
273	for _, tt := range testCases {
274		require.Equal(t, tt.equal, tt.source.Equal(tt.target))
275	}
276}
277
278func TestPiecesAdd(t *testing.T) {
279	node0 := testrand.NodeID()
280	node1 := testrand.NodeID()
281	node2 := testrand.NodeID()
282	node3 := testrand.NodeID()
283
284	tests := []struct {
285		name        string
286		pieces      metabase.Pieces
287		piecesToAdd metabase.Pieces
288		want        metabase.Pieces
289		wantErr     string
290	}{
291		{
292			name: "piece exists",
293			pieces: metabase.Pieces{
294				metabase.Piece{
295					Number:      0,
296					StorageNode: node0,
297				},
298				metabase.Piece{
299					Number:      1,
300					StorageNode: node1,
301				},
302			},
303			piecesToAdd: metabase.Pieces{
304				metabase.Piece{
305					Number:      1,
306					StorageNode: node1,
307				},
308			},
309			wantErr: "metabase: piece to add already exists (piece no: 1)",
310			want:    metabase.Pieces{},
311		},
312
313		{
314			name: "pieces added",
315			pieces: metabase.Pieces{
316				metabase.Piece{
317					Number:      0,
318					StorageNode: node0,
319				},
320				metabase.Piece{
321					Number:      3,
322					StorageNode: node3,
323				},
324			},
325			piecesToAdd: metabase.Pieces{
326				metabase.Piece{
327					Number:      2,
328					StorageNode: node2,
329				},
330				metabase.Piece{
331					Number:      1,
332					StorageNode: node1,
333				},
334			},
335			wantErr: "",
336			want: metabase.Pieces{
337				metabase.Piece{
338					Number:      0,
339					StorageNode: node0,
340				},
341				metabase.Piece{
342					Number:      1,
343					StorageNode: node1,
344				},
345				metabase.Piece{
346					Number:      2,
347					StorageNode: node2,
348				},
349				metabase.Piece{
350					Number:      3,
351					StorageNode: node3,
352				},
353			},
354		},
355		{
356			name:   "adding new pieces to empty piece",
357			pieces: metabase.Pieces{},
358			piecesToAdd: metabase.Pieces{
359				metabase.Piece{
360					Number:      1,
361					StorageNode: node1,
362				},
363				metabase.Piece{
364					Number:      0,
365					StorageNode: node0,
366				},
367			},
368			wantErr: "",
369			want: metabase.Pieces{
370				metabase.Piece{
371					Number:      0,
372					StorageNode: node0,
373				},
374				metabase.Piece{
375					Number:      1,
376					StorageNode: node1,
377				},
378			},
379		},
380		{
381			name: "adding empty piece",
382			pieces: metabase.Pieces{
383				metabase.Piece{
384					Number:      0,
385					StorageNode: node0,
386				},
387				metabase.Piece{
388					Number:      1,
389					StorageNode: node1,
390				},
391			},
392			piecesToAdd: metabase.Pieces{},
393			wantErr:     "",
394			want: metabase.Pieces{
395				metabase.Piece{
396					Number:      0,
397					StorageNode: node0,
398				},
399				metabase.Piece{
400					Number:      1,
401					StorageNode: node1,
402				},
403			},
404		},
405		{
406			name:        "adding empty piece to empty pieces",
407			pieces:      metabase.Pieces{},
408			piecesToAdd: metabase.Pieces{},
409			wantErr:     "",
410			want:        metabase.Pieces{},
411		},
412	}
413	for _, tt := range tests {
414		t.Run(tt.name, func(t *testing.T) {
415			require.NotNil(t, tt.pieces, tt.name)
416			got, err := tt.pieces.Add(tt.piecesToAdd)
417
418			if tt.wantErr != "" {
419				require.EqualError(t, err, tt.wantErr, tt.name)
420			} else {
421				require.NoError(t, err, tt.name)
422			}
423			require.Equal(t, got, tt.want, tt.name)
424		})
425	}
426}
427
428func TestPiecesRemove(t *testing.T) {
429	node0 := testrand.NodeID()
430	node1 := testrand.NodeID()
431	node2 := testrand.NodeID()
432	node3 := testrand.NodeID()
433
434	tests := []struct {
435		name           string
436		pieces         metabase.Pieces
437		piecesToRemove metabase.Pieces
438		want           metabase.Pieces
439		wantErr        string
440	}{
441		{
442			name:   "piece missing",
443			pieces: metabase.Pieces{},
444			piecesToRemove: metabase.Pieces{
445				metabase.Piece{
446					Number:      1,
447					StorageNode: node1,
448				},
449			},
450			wantErr: "metabase: invalid request: pieces missing",
451			want:    metabase.Pieces{},
452		},
453		{
454			name: "piecesToRemove struct is empty",
455			pieces: metabase.Pieces{
456				metabase.Piece{
457					Number:      1,
458					StorageNode: node1,
459				},
460			},
461			piecesToRemove: metabase.Pieces{},
462			wantErr:        "",
463			want: metabase.Pieces{
464				metabase.Piece{
465					Number:      1,
466					StorageNode: node1,
467				},
468			},
469		},
470		{
471			name:           "both pieces and piecesToRemove struct are empty",
472			pieces:         metabase.Pieces{},
473			piecesToRemove: metabase.Pieces{},
474			wantErr:        "metabase: invalid request: pieces missing",
475			want:           metabase.Pieces{},
476		},
477		{
478			name: "pieces removed",
479			pieces: metabase.Pieces{
480				metabase.Piece{
481					Number:      0,
482					StorageNode: node0,
483				},
484				metabase.Piece{
485					Number:      1,
486					StorageNode: node1,
487				},
488				metabase.Piece{
489					Number:      2,
490					StorageNode: node2,
491				},
492				metabase.Piece{
493					Number:      3,
494					StorageNode: node3,
495				},
496			},
497			piecesToRemove: metabase.Pieces{
498				metabase.Piece{
499					Number:      2,
500					StorageNode: node2,
501				},
502				metabase.Piece{
503					Number:      1,
504					StorageNode: node1,
505				},
506			},
507			wantErr: "",
508			want: metabase.Pieces{
509				metabase.Piece{
510					Number:      0,
511					StorageNode: node0,
512				},
513				metabase.Piece{
514					Number:      3,
515					StorageNode: node3,
516				},
517			},
518		},
519	}
520	for _, tt := range tests {
521		t.Run(tt.name, func(t *testing.T) {
522			require.NotNil(t, tt.pieces, tt.name)
523			got, err := tt.pieces.Remove(tt.piecesToRemove)
524
525			if tt.wantErr != "" {
526				require.EqualError(t, err, tt.wantErr, tt.name)
527			} else {
528				require.NoError(t, err, tt.name)
529			}
530			require.Equal(t, got, tt.want, tt.name)
531		})
532	}
533}
534
535func TestPiecesUpdate(t *testing.T) {
536	node0 := testrand.NodeID()
537	node1 := testrand.NodeID()
538	node2 := testrand.NodeID()
539	node3 := testrand.NodeID()
540
541	tests := []struct {
542		name           string
543		pieces         metabase.Pieces
544		piecesToAdd    metabase.Pieces
545		piecesToRemove metabase.Pieces
546		want           metabase.Pieces
547		wantErr        string
548	}{
549		{
550			name: "add and remove pieces",
551			pieces: metabase.Pieces{
552				metabase.Piece{
553					Number:      0,
554					StorageNode: node0,
555				},
556				metabase.Piece{
557					Number:      1,
558					StorageNode: node1,
559				},
560				metabase.Piece{
561					Number:      2,
562					StorageNode: node2,
563				},
564			},
565			piecesToRemove: metabase.Pieces{
566				metabase.Piece{
567					Number:      0,
568					StorageNode: node0,
569				},
570			},
571			piecesToAdd: metabase.Pieces{
572				metabase.Piece{
573					Number:      3,
574					StorageNode: node3,
575				},
576			},
577			wantErr: "",
578			want: metabase.Pieces{
579				metabase.Piece{
580					Number:      1,
581					StorageNode: node1,
582				},
583				metabase.Piece{
584					Number:      2,
585					StorageNode: node2,
586				},
587				metabase.Piece{
588					Number:      3,
589					StorageNode: node3,
590				},
591			},
592		},
593		{
594			name: "add pieces only",
595			pieces: metabase.Pieces{
596				metabase.Piece{
597					Number:      1,
598					StorageNode: node1,
599				},
600				metabase.Piece{
601					Number:      2,
602					StorageNode: node2,
603				},
604			},
605			piecesToRemove: metabase.Pieces{},
606			piecesToAdd: metabase.Pieces{
607				metabase.Piece{
608					Number:      0,
609					StorageNode: node0,
610				},
611			},
612			wantErr: "",
613			want: metabase.Pieces{
614				metabase.Piece{
615					Number:      0,
616					StorageNode: node0,
617				},
618				metabase.Piece{
619					Number:      1,
620					StorageNode: node1,
621				},
622				metabase.Piece{
623					Number:      2,
624					StorageNode: node2,
625				},
626			},
627		},
628		{
629			name: "remove pieces only",
630			pieces: metabase.Pieces{
631				metabase.Piece{
632					Number:      1,
633					StorageNode: node1,
634				},
635				metabase.Piece{
636					Number:      2,
637					StorageNode: node2,
638				},
639			},
640			piecesToRemove: metabase.Pieces{
641				metabase.Piece{
642					Number:      2,
643					StorageNode: node2,
644				},
645			},
646			piecesToAdd: metabase.Pieces{},
647			wantErr:     "",
648			want: metabase.Pieces{
649				metabase.Piece{
650					Number:      1,
651					StorageNode: node1,
652				},
653			},
654		},
655		{
656			name: "both piecesToAdd and piecesToRemove are empty",
657			pieces: metabase.Pieces{
658				metabase.Piece{
659					Number:      1,
660					StorageNode: node1,
661				},
662				metabase.Piece{
663					Number:      2,
664					StorageNode: node2,
665				},
666			},
667			piecesToRemove: metabase.Pieces{},
668			piecesToAdd:    metabase.Pieces{},
669			wantErr:        "",
670			want: metabase.Pieces{
671				metabase.Piece{
672					Number:      1,
673					StorageNode: node1,
674				},
675				metabase.Piece{
676					Number:      2,
677					StorageNode: node2,
678				},
679			},
680		},
681		{
682			name:   "updating empty pieces",
683			pieces: metabase.Pieces{},
684			piecesToRemove: metabase.Pieces{
685				metabase.Piece{
686					Number:      1,
687					StorageNode: node1,
688				},
689			},
690			piecesToAdd: metabase.Pieces{
691				metabase.Piece{
692					Number:      0,
693					StorageNode: node1,
694				},
695			},
696			wantErr: "",
697			want: metabase.Pieces{
698				metabase.Piece{
699					Number:      0,
700					StorageNode: node1,
701				},
702			},
703		},
704	}
705	for _, tt := range tests {
706		t.Run(tt.name, func(t *testing.T) {
707			require.NotNil(t, tt.pieces, tt.name)
708			got, err := tt.pieces.Update(tt.piecesToAdd, tt.piecesToRemove)
709
710			if tt.wantErr != "" {
711				require.EqualError(t, err, tt.wantErr, tt.name)
712			} else {
713				require.NoError(t, err, tt.name)
714			}
715			require.Equal(t, got, tt.want, tt.name)
716		})
717	}
718}
719