// Copyright (c) 2015-2021 MinIO, Inc. // // This file is part of MinIO Object Storage stack // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package cmd import ( "slices" "sort" "testing" "time" "github.com/minio/minio/internal/bucket/lifecycle" xhttp "github.com/minio/minio/internal/http" ) func Test_hashDeterministicString(t *testing.T) { tests := []struct { name string arg map[string]string }{ { name: "zero", arg: map[string]string{}, }, { name: "nil", arg: nil, }, { name: "one", arg: map[string]string{"key": "value"}, }, { name: "several", arg: map[string]string{ xhttp.AmzRestore: "FAILED", xhttp.ContentMD5: mustGetUUID(), xhttp.AmzBucketReplicationStatus: "PENDING", xhttp.ContentType: "application/json", }, }, { name: "someempty", arg: map[string]string{ xhttp.AmzRestore: "", xhttp.ContentMD5: mustGetUUID(), xhttp.AmzBucketReplicationStatus: "", xhttp.ContentType: "application/json", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { const n = 100 want := hashDeterministicString(tt.arg) m := tt.arg for i := 0; i < n; i++ { if got := hashDeterministicString(m); got != want { t.Errorf("hashDeterministicString() = %v, want %v", got, want) } } // Check casual collisions if m == nil { m = make(map[string]string) } m["12312312"] = "" if got := hashDeterministicString(m); got == want { t.Errorf("hashDeterministicString() = %v, does not want %v", got, want) } want = hashDeterministicString(m) delete(m, "12312312") m["another"] = "" if got := hashDeterministicString(m); got == want { t.Errorf("hashDeterministicString() = %v, does not want %v", got, want) } want = hashDeterministicString(m) m["another"] = "hashDeterministicString" if got := hashDeterministicString(m); got == want { t.Errorf("hashDeterministicString() = %v, does not want %v", got, want) } want = hashDeterministicString(m) m["another"] = "hashDeterministicStringhashDeterministicStringhashDeterministicStringhashDeterministicStringhashDeterministicStringhashDeterministicStringhashDeterministicString" if got := hashDeterministicString(m); got == want { t.Errorf("hashDeterministicString() = %v, does not want %v", got, want) } // Flip key/value want = hashDeterministicString(m) delete(m, "another") m["hashDeterministicStringhashDeterministicStringhashDeterministicStringhashDeterministicStringhashDeterministicStringhashDeterministicStringhashDeterministicString"] = "another" if got := hashDeterministicString(m); got == want { t.Errorf("hashDeterministicString() = %v, does not want %v", got, want) } }) } } func TestGetFileInfoVersions(t *testing.T) { basefi := FileInfo{ Volume: "volume", Name: "object-name", VersionID: "756100c6-b393-4981-928a-d49bbc164741", IsLatest: true, Deleted: false, TransitionStatus: "", DataDir: "bffea160-ca7f-465f-98bc-9b4f1c3ba1ef", XLV1: false, ModTime: time.Now().UTC(), Size: 0, Mode: 0, Metadata: nil, Parts: nil, Erasure: ErasureInfo{ Algorithm: ReedSolomon.String(), DataBlocks: 4, ParityBlocks: 2, BlockSize: 10000, Index: 1, Distribution: []int{1, 2, 3, 4, 5, 6, 7, 8}, Checksums: []ChecksumInfo{{ PartNumber: 1, Algorithm: HighwayHash256S, Hash: nil, }}, }, MarkDeleted: false, NumVersions: 1, SuccessorModTime: time.Time{}, } xl := xlMetaV2{} var versions []FileInfo var allVersionIDs, freeVersionIDs []string for i := 0; i < 5; i++ { fi := basefi fi.VersionID = mustGetUUID() fi.DataDir = mustGetUUID() fi.ModTime = basefi.ModTime.Add(time.Duration(i) * time.Second) if err := xl.AddVersion(fi); err != nil { t.Fatalf("%d: Failed to add version %v", i+1, err) } if i > 3 { // Simulate transition of a version transfi := fi transfi.TransitionStatus = lifecycle.TransitionComplete transfi.TransitionTier = "MINIO-TIER" transfi.TransitionedObjName = mustGetUUID() xl.DeleteVersion(transfi) fi.SetTierFreeVersionID(mustGetUUID()) // delete this version leading to a free version xl.DeleteVersion(fi) freeVersionIDs = append(freeVersionIDs, fi.TierFreeVersionID()) allVersionIDs = append(allVersionIDs, fi.TierFreeVersionID()) } else { versions = append(versions, fi) allVersionIDs = append(allVersionIDs, fi.VersionID) } } buf, err := xl.AppendTo(nil) if err != nil { t.Fatalf("Failed to serialize xlmeta %v", err) } fivs, err := getFileInfoVersions(buf, basefi.Volume, basefi.Name, true, false) if err != nil { t.Fatalf("getFileInfoVersions failed: %v", err) } chkNumVersions := func(fis []FileInfo) bool { for i := 0; i < len(fis)-1; i++ { if fis[i].NumVersions != fis[i+1].NumVersions { return false } } return true } if !chkNumVersions(fivs.Versions) { t.Fatalf("Expected all versions to have the same NumVersions") } sort.Slice(versions, func(i, j int) bool { if versions[i].IsLatest { return true } if versions[j].IsLatest { return false } return versions[i].ModTime.After(versions[j].ModTime) }) for i, fi := range fivs.Versions { if fi.VersionID != versions[i].VersionID { t.Fatalf("getFileInfoVersions: versions don't match at %d, version id expected %s but got %s", i, fi.VersionID, versions[i].VersionID) } if fi.NumVersions != len(fivs.Versions) { t.Fatalf("getFileInfoVersions: version with %s version id expected to have %d as NumVersions but got %d", fi.VersionID, len(fivs.Versions), fi.NumVersions) } } for i, free := range fivs.FreeVersions { if free.VersionID != freeVersionIDs[i] { t.Fatalf("getFileInfoVersions: free versions don't match at %d, version id expected %s but got %s", i, free.VersionID, freeVersionIDs[i]) } } // versions are stored in xl-meta sorted in descending order of their ModTime slices.Reverse(allVersionIDs) fivs, err = getFileInfoVersions(buf, basefi.Volume, basefi.Name, true, true) if err != nil { t.Fatalf("getFileInfoVersions failed: %v", err) } if !chkNumVersions(fivs.Versions) { t.Fatalf("Expected all versions to have the same NumVersions") } for i, fi := range fivs.Versions { if fi.VersionID != allVersionIDs[i] { t.Fatalf("getFileInfoVersions: all versions don't match at %d expected %s but got %s", i, allVersionIDs[i], fi.VersionID) } } }