2021-11-18 19:09:12 -05:00
// 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/>.
2021-11-18 15:15:22 -05:00
package cmd
import (
2024-06-07 18:18:44 -04:00
"slices"
2021-11-19 20:54:10 -05:00
"sort"
2021-11-18 15:15:22 -05:00
"testing"
2021-11-19 20:54:10 -05:00
"time"
2021-11-18 15:15:22 -05:00
2021-11-19 20:54:10 -05:00
"github.com/minio/minio/internal/bucket/lifecycle"
2021-11-18 15:15:22 -05:00
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 )
}
} )
}
}
2021-11-19 20:54:10 -05:00
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
2024-06-07 18:18:44 -04:00
var allVersionIDs , freeVersionIDs [ ] string
2021-11-19 20:54:10 -05:00
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 ( ) )
2024-06-07 18:18:44 -04:00
allVersionIDs = append ( allVersionIDs , fi . TierFreeVersionID ( ) )
2021-11-19 20:54:10 -05:00
} else {
versions = append ( versions , fi )
2024-06-07 18:18:44 -04:00
allVersionIDs = append ( allVersionIDs , fi . VersionID )
2021-11-19 20:54:10 -05:00
}
}
buf , err := xl . AppendTo ( nil )
if err != nil {
t . Fatalf ( "Failed to serialize xlmeta %v" , err )
}
2024-06-07 18:18:44 -04:00
fivs , err := getFileInfoVersions ( buf , basefi . Volume , basefi . Name , true , false )
2021-11-19 20:54:10 -05:00
if err != nil {
t . Fatalf ( "getFileInfoVersions failed: %v" , err )
}
2024-06-07 18:18:44 -04:00
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" )
}
2021-11-19 20:54:10 -05:00
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 )
}
2024-06-07 18:18:44 -04:00
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 )
}
2021-11-19 20:54:10 -05:00
}
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 ] )
}
}
2024-06-07 18:18:44 -04:00
// 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 )
}
}
2021-11-19 20:54:10 -05:00
}