mirror of https://github.com/minio/minio.git
238 lines
7.1 KiB
Go
238 lines
7.1 KiB
Go
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|