github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/core/lom_test.go (about)

     1  //nolint:dupl // copy-paste benign and can wait
     2  // Package core_test provides tests for cluster package
     3  /*
     4   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     5   */
     6  package core_test
     7  
     8  import (
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"path/filepath"
    13  	"strconv"
    14  	"time"
    15  
    16  	"github.com/NVIDIA/aistore/api/apc"
    17  	"github.com/NVIDIA/aistore/cmn"
    18  	"github.com/NVIDIA/aistore/cmn/cos"
    19  	"github.com/NVIDIA/aistore/core"
    20  	"github.com/NVIDIA/aistore/core/meta"
    21  	"github.com/NVIDIA/aistore/core/mock"
    22  	"github.com/NVIDIA/aistore/fs"
    23  	"github.com/NVIDIA/aistore/tools/cryptorand"
    24  	. "github.com/onsi/ginkgo/v2"
    25  	. "github.com/onsi/gomega"
    26  )
    27  
    28  var _ = Describe("LOM", func() {
    29  	const (
    30  		tmpDir    = "/tmp/lom_test"
    31  		numMpaths = 3
    32  
    33  		bucketLocalA = "LOM_TEST_Local_A"
    34  		bucketLocalB = "LOM_TEST_Local_B"
    35  		bucketLocalC = "LOM_TEST_Local_C"
    36  
    37  		bucketCloudA = "LOM_TEST_Cloud_A"
    38  		bucketCloudB = "LOM_TEST_Cloud_B"
    39  
    40  		sameBucketName = "LOM_TEST_Local_and_Cloud"
    41  	)
    42  
    43  	var (
    44  		localBckA = cmn.Bck{Name: bucketLocalA, Provider: apc.AIS, Ns: cmn.NsGlobal}
    45  		localBckB = cmn.Bck{Name: bucketLocalB, Provider: apc.AIS, Ns: cmn.NsGlobal}
    46  		cloudBckA = cmn.Bck{Name: bucketCloudA, Provider: apc.AWS, Ns: cmn.NsGlobal}
    47  	)
    48  
    49  	var (
    50  		mpaths []string
    51  		mis    []*fs.Mountpath
    52  
    53  		oldCloudProviders = cmn.GCO.Get().Backend.Providers
    54  	)
    55  
    56  	for i := range numMpaths {
    57  		mpath := fmt.Sprintf("%s/mpath%d", tmpDir, i)
    58  		mpaths = append(mpaths, mpath)
    59  		mis = append(mis, &fs.Mountpath{Path: mpath})
    60  		_ = cos.CreateDir(mpath)
    61  	}
    62  
    63  	config := cmn.GCO.BeginUpdate()
    64  	config.TestFSP.Count = 1
    65  	cmn.GCO.CommitUpdate(config)
    66  
    67  	fs.TestNew(nil)
    68  	for _, mpath := range mpaths {
    69  		_, _ = fs.Add(mpath, "daeID")
    70  	}
    71  
    72  	fs.CSM.Reg(fs.ObjectType, &fs.ObjectContentResolver{}, true)
    73  	fs.CSM.Reg(fs.WorkfileType, &fs.WorkfileContentResolver{}, true)
    74  
    75  	bmd := mock.NewBaseBownerMock(
    76  		meta.NewBck(
    77  			bucketLocalA, apc.AIS, cmn.NsGlobal,
    78  			&cmn.Bprops{Cksum: cmn.CksumConf{Type: cos.ChecksumNone}, BID: 1},
    79  		),
    80  		meta.NewBck(
    81  			bucketLocalB, apc.AIS, cmn.NsGlobal,
    82  			&cmn.Bprops{Cksum: cmn.CksumConf{Type: cos.ChecksumXXHash}, LRU: cmn.LRUConf{Enabled: true}, BID: 2},
    83  		),
    84  		meta.NewBck(
    85  			bucketLocalC, apc.AIS, cmn.NsGlobal,
    86  			&cmn.Bprops{
    87  				Cksum:  cmn.CksumConf{Type: cos.ChecksumXXHash},
    88  				LRU:    cmn.LRUConf{Enabled: true},
    89  				Mirror: cmn.MirrorConf{Enabled: true, Copies: 2},
    90  				BID:    3,
    91  			},
    92  		),
    93  		meta.NewBck(sameBucketName, apc.AIS, cmn.NsGlobal, &cmn.Bprops{BID: 4}),
    94  		meta.NewBck(bucketCloudA, apc.AWS, cmn.NsGlobal, &cmn.Bprops{BID: 5}),
    95  		meta.NewBck(bucketCloudB, apc.AWS, cmn.NsGlobal, &cmn.Bprops{BID: 6}),
    96  		meta.NewBck(sameBucketName, apc.AWS, cmn.NsGlobal, &cmn.Bprops{BID: 7}),
    97  	)
    98  
    99  	BeforeEach(func() {
   100  		// Dummy backend provider for tests involving cloud buckets
   101  		config := cmn.GCO.BeginUpdate()
   102  		config.Backend.Providers = map[string]cmn.Ns{
   103  			apc.AWS: cmn.NsGlobal,
   104  		}
   105  
   106  		cmn.GCO.CommitUpdate(config)
   107  
   108  		for _, mpath := range mpaths {
   109  			_, _ = fs.Add(mpath, "daeID")
   110  		}
   111  		_ = mock.NewTarget(bmd)
   112  	})
   113  
   114  	AfterEach(func() {
   115  		_ = os.RemoveAll(tmpDir)
   116  
   117  		config := cmn.GCO.BeginUpdate()
   118  		config.Backend.Providers = oldCloudProviders
   119  		cmn.GCO.CommitUpdate(config)
   120  	})
   121  
   122  	Describe("FQN Resolution", func() {
   123  		testObject := "foldr/test-obj.ext"
   124  		desiredLocalFQN := mis[0].MakePathFQN(&localBckA, fs.ObjectType, testObject)
   125  
   126  		When("run for an ais bucket", func() {
   127  			It("Should populate fields from Bucket and ObjName", func() {
   128  				fs.Disable(mpaths[1]) // Ensure that it matches desiredLocalFQN
   129  				fs.Disable(mpaths[2]) // Ensure that it matches desiredLocalFQN
   130  
   131  				lom := &core.LOM{ObjName: testObject}
   132  				err := lom.InitBck(&cmn.Bck{Name: bucketLocalA, Provider: apc.AIS})
   133  				Expect(err).NotTo(HaveOccurred())
   134  				Expect(lom.FQN).To(BeEquivalentTo(desiredLocalFQN))
   135  
   136  				Expect(lom.Uname()).To(BeEquivalentTo(lom.Bck().MakeUname(testObject)))
   137  				Expect(lom.Bck().Provider).To(Equal(apc.AIS))
   138  
   139  				// from lom.go: redundant in-part; tradeoff to speed-up workfile name gen, etc.
   140  				Expect(lom.Mountpath().Path).To(BeEquivalentTo(mpaths[0]))
   141  				expectEqualBck(lom.Bucket(), &localBckA)
   142  				Expect(lom.ObjName).To(BeEquivalentTo(testObject))
   143  
   144  				fs.Enable(mpaths[1])
   145  				fs.Enable(mpaths[2])
   146  			})
   147  
   148  			It("Should populate fields from a FQN", func() {
   149  				lom := &core.LOM{}
   150  				err := lom.InitFQN(desiredLocalFQN, nil)
   151  				Expect(err).NotTo(HaveOccurred())
   152  				Expect(lom.Bck().Name).To(BeEquivalentTo(bucketLocalA))
   153  				Expect(lom.ObjName).To(BeEquivalentTo(testObject))
   154  
   155  				Expect(lom.Uname()).To(BeEquivalentTo(lom.Bck().MakeUname(testObject)))
   156  				Expect(lom.Bck().Provider).To(Equal(apc.AIS))
   157  
   158  				// from lom.go: redundant in-part; tradeoff to speed-up workfile name gen, etc.
   159  				Expect(lom.Mountpath().Path).To(BeEquivalentTo(mpaths[0]))
   160  				expectEqualBck(lom.Bucket(), &localBckA)
   161  				Expect(lom.ObjName).To(BeEquivalentTo(testObject))
   162  			})
   163  
   164  			It("Should resolve work files", func() {
   165  				testPid := strconv.FormatInt(9876, 16)
   166  				testTieIndex := strconv.FormatInt(1355314332000000, 16)[5:]
   167  				workObject := "foldr/get.test-obj.ext" + "." + testTieIndex + "." + testPid
   168  				localFQN := mis[0].MakePathFQN(&cloudBckA, fs.WorkfileType, workObject)
   169  
   170  				var parsed fs.ParsedFQN
   171  				_, err := core.ResolveFQN(localFQN, &parsed)
   172  				Expect(err).NotTo(HaveOccurred())
   173  				Expect(parsed.ContentType).To(BeEquivalentTo(fs.WorkfileType))
   174  			})
   175  		})
   176  
   177  		When("run for a cloud bucket", func() {
   178  			testObject := "foldr/test-obj.ext"
   179  			desiredCloudFQN := mis[0].MakePathFQN(&cloudBckA, fs.ObjectType, testObject)
   180  
   181  			It("Should populate fields from Bucket and ObjName", func() {
   182  				// Ensure that it matches desiredCloudFQN
   183  				fs.Disable(mpaths[1])
   184  				fs.Disable(mpaths[2])
   185  
   186  				lom := &core.LOM{ObjName: testObject}
   187  				err := lom.InitBck(&cmn.Bck{Name: bucketCloudA, Provider: apc.AWS, Ns: cmn.NsGlobal})
   188  				Expect(err).NotTo(HaveOccurred())
   189  				Expect(lom.FQN).To(BeEquivalentTo(desiredCloudFQN))
   190  
   191  				Expect(lom.Uname()).To(BeEquivalentTo(lom.Bck().MakeUname(testObject)))
   192  				Expect(lom.Bck().Provider).To(Equal(apc.AWS))
   193  
   194  				// from lom.go: redundant in-part; tradeoff to speed-up workfile name gen, etc.
   195  				Expect(lom.Mountpath().Path).To(Equal(mpaths[0]))
   196  				expectEqualBck(lom.Bucket(), &cloudBckA)
   197  				Expect(lom.ObjName).To(Equal(testObject))
   198  
   199  				fs.Enable(mpaths[2])
   200  				fs.Enable(mpaths[1])
   201  			})
   202  
   203  			It("Should populate fields from a FQN", func() {
   204  				lom := &core.LOM{}
   205  				err := lom.InitFQN(desiredCloudFQN, nil)
   206  				Expect(err).NotTo(HaveOccurred())
   207  				Expect(lom.Bck().Name).To(BeEquivalentTo(bucketCloudA))
   208  				Expect(lom.ObjName).To(BeEquivalentTo(testObject))
   209  
   210  				Expect(lom.Uname()).To(BeEquivalentTo(lom.Bck().MakeUname(testObject)))
   211  				Expect(lom.Bck().Provider).To(Equal(apc.AWS))
   212  
   213  				// from lom.go: redundant in-part; tradeoff to speed-up workfile name gen, etc.
   214  				Expect(lom.Mountpath().Path).To(Equal(mpaths[0]))
   215  				expectEqualBck(lom.Bucket(), &cloudBckA)
   216  				Expect(lom.ObjName).To(Equal(testObject))
   217  			})
   218  		})
   219  
   220  		When("run for invalid FQN", func() {
   221  			DescribeTable("should return error",
   222  				func(fqn string) {
   223  					lom := &core.LOM{}
   224  					err := lom.InitFQN(fqn, nil)
   225  					Expect(err).To(HaveOccurred())
   226  				},
   227  				Entry(
   228  					"invalid object name",
   229  					mis[0].MakePathFQN(
   230  						&cmn.Bck{Name: bucketCloudA, Provider: apc.AIS, Ns: cmn.NsGlobal},
   231  						fs.ObjectType,
   232  						" ??? ",
   233  					),
   234  				),
   235  				Entry(
   236  					"invalid fqn",
   237  					"?/.,",
   238  				),
   239  				Entry(
   240  					"missing content type",
   241  					mpaths[0],
   242  				),
   243  				Entry(
   244  					"missing bucket type",
   245  					filepath.Join(mpaths[0], fs.ObjectType),
   246  				),
   247  				Entry(
   248  					"missing bucket",
   249  					mis[0].MakePathBck(
   250  						&cmn.Bck{Name: "", Provider: apc.AIS, Ns: cmn.NsGlobal},
   251  					),
   252  				),
   253  				Entry(
   254  					"missing object",
   255  					mis[0].MakePathCT(
   256  						&cmn.Bck{Name: bucketLocalA, Provider: apc.AIS, Ns: cmn.NsGlobal},
   257  						fs.ObjectType,
   258  					),
   259  				),
   260  			)
   261  		})
   262  	})
   263  
   264  	Describe("Load", func() {
   265  		Describe("Exists", func() {
   266  			testFileSize := 123
   267  			testObjectName := "fstat-foldr/test-obj.ext"
   268  			localFQN := mis[0].MakePathFQN(&localBckA, fs.ObjectType, testObjectName)
   269  
   270  			It("should find out that object does not exist", func() {
   271  				os.Remove(localFQN)
   272  				lom := &core.LOM{}
   273  				err := lom.InitFQN(localFQN, nil)
   274  				Expect(err).NotTo(HaveOccurred())
   275  				err = lom.Load(false, false)
   276  				Expect(cos.IsNotExist(err, 0)).To(BeTrue())
   277  			})
   278  
   279  			It("should find out that object exists", func() {
   280  				createTestFile(localFQN, testFileSize)
   281  				lom := &core.LOM{}
   282  				err := lom.InitFQN(localFQN, nil)
   283  				lom.SetSize(int64(testFileSize))
   284  				Expect(err).NotTo(HaveOccurred())
   285  				Expect(persist(lom)).NotTo(HaveOccurred())
   286  				err = lom.Load(false, false)
   287  				Expect(err).NotTo(HaveOccurred())
   288  				Expect(lom.SizeBytes()).To(BeEquivalentTo(testFileSize))
   289  			})
   290  		})
   291  
   292  		Describe("Atime", func() {
   293  			desiredAtime := time.Unix(1500000000, 0)
   294  			testObjectName := "foldr/test-obj.ext"
   295  
   296  			It("should fetch atime for bucket with LRU disabled", func() {
   297  				localFQN := mis[0].MakePathFQN(&localBckA, fs.ObjectType, testObjectName)
   298  				createTestFile(localFQN, 0)
   299  				Expect(os.Chtimes(localFQN, desiredAtime, desiredAtime)).ShouldNot(HaveOccurred())
   300  
   301  				lom := &core.LOM{}
   302  				err := lom.InitFQN(localFQN, nil)
   303  				Expect(err).NotTo(HaveOccurred())
   304  				lom.AcquireAtimefs()
   305  				Expect(lom.Persist()).NotTo(HaveOccurred())
   306  				err = lom.Load(false, false)
   307  				Expect(err).NotTo(HaveOccurred())
   308  
   309  				Expect(time.Unix(0, lom.AtimeUnix())).To(BeEquivalentTo(desiredAtime))
   310  			})
   311  			It("should fetch atime for bucket with LRU enabled", func() {
   312  				localFQN := mis[0].MakePathFQN(&localBckB, fs.ObjectType, testObjectName)
   313  				createTestFile(localFQN, 0)
   314  				Expect(os.Chtimes(localFQN, desiredAtime, desiredAtime)).ShouldNot(HaveOccurred())
   315  
   316  				lom := &core.LOM{}
   317  				err := lom.InitFQN(localFQN, nil)
   318  				Expect(err).NotTo(HaveOccurred())
   319  				lom.AcquireAtimefs()
   320  				Expect(lom.Persist()).NotTo(HaveOccurred())
   321  				err = lom.Load(false, false)
   322  				Expect(err).NotTo(HaveOccurred())
   323  
   324  				Expect(time.Unix(0, lom.AtimeUnix())).To(BeEquivalentTo(desiredAtime))
   325  			})
   326  		})
   327  
   328  		Describe("checksum", func() {
   329  			testFileSize := 456
   330  			testObjectName := "cksum-foldr/test-obj.ext"
   331  			// Bucket needs to have checksum enabled
   332  			localFQN := mis[0].MakePathFQN(&localBckB, fs.ObjectType, testObjectName)
   333  			dummyCksm := cos.NewCksum(cos.ChecksumXXHash, "dummycksm")
   334  
   335  			Describe("ComputeCksumIfMissing", func() {
   336  				It("should ignore if bucket checksum is none", func() {
   337  					testObject := "foldr/test-obj.ext"
   338  					noneFQN := mis[0].MakePathFQN(&localBckA, fs.ObjectType, testObject)
   339  
   340  					lom := NewBasicLom(noneFQN)
   341  					cksum, err := lom.ComputeSetCksum()
   342  					Expect(err).NotTo(HaveOccurred())
   343  					Expect(cksum).To(BeNil())
   344  				})
   345  
   346  				It("should not compute if not missing", func() {
   347  					lom := filePut(localFQN, testFileSize)
   348  					lom.SetCksum(dummyCksm)
   349  					cksum, err := lom.ComputeSetCksum()
   350  					Expect(err).NotTo(HaveOccurred())
   351  					Expect(cksum).NotTo(BeEquivalentTo(dummyCksm))
   352  					Expect(cksum.Value()).NotTo(BeEquivalentTo(""))
   353  					_, err = lom.ComputeSetCksum()
   354  					Expect(err).NotTo(HaveOccurred())
   355  					Expect(lom.Checksum()).To(BeEquivalentTo(cksum))
   356  				})
   357  
   358  				It("should compute missing checksum", func() {
   359  					lom := filePut(localFQN, testFileSize)
   360  					expectedChecksum := getTestFileHash(localFQN)
   361  
   362  					cksum, err := lom.ComputeSetCksum()
   363  					Expect(err).NotTo(HaveOccurred())
   364  					cksumType, cksumValue := cksum.Get()
   365  					Expect(cksumType).To(BeEquivalentTo(cos.ChecksumXXHash))
   366  					Expect(cksumValue).To(BeEquivalentTo(expectedChecksum))
   367  					Expect(lom.Checksum().Equal(cksum)).To(BeTrue())
   368  
   369  					newLom := NewBasicLom(lom.FQN)
   370  					err = newLom.Load(false, false)
   371  					Expect(err).NotTo(HaveOccurred())
   372  					cksumType, _ = newLom.Checksum().Get()
   373  					Expect(cksumType).To(BeEquivalentTo(cos.ChecksumNone))
   374  				})
   375  			})
   376  
   377  			Describe("ValidateMetaChecksum", func() {
   378  				It("should ignore if bucket checksum is none", func() {
   379  					testObject := "foldr/test-obj.ext"
   380  					noneFQN := mis[0].MakePathFQN(&localBckA, fs.ObjectType, testObject)
   381  
   382  					lom := NewBasicLom(noneFQN)
   383  					err := lom.ValidateMetaChecksum()
   384  					Expect(err).NotTo(HaveOccurred())
   385  					Expect(lom.Checksum()).To(BeNil())
   386  				})
   387  
   388  				It("should fill object with checksum if was not present", func() {
   389  					lom := filePut(localFQN, testFileSize)
   390  					expectedChecksum := getTestFileHash(localFQN)
   391  
   392  					fsLOM := NewBasicLom(localFQN)
   393  					err := fsLOM.Load(false, false)
   394  					Expect(err).NotTo(HaveOccurred())
   395  
   396  					cksumType, _ := fsLOM.Checksum().Get()
   397  					Expect(cksumType).To(BeEquivalentTo(cos.ChecksumNone))
   398  
   399  					Expect(lom.ValidateContentChecksum()).NotTo(HaveOccurred())
   400  					lom.UncacheUnless()
   401  
   402  					Expect(lom.Checksum()).ToNot(BeNil())
   403  					_, val := lom.Checksum().Get()
   404  					fsLOM = NewBasicLom(localFQN)
   405  					err = fsLOM.Load(false, false)
   406  					Expect(err).ShouldNot(HaveOccurred())
   407  					_, fsVal := fsLOM.Checksum().Get()
   408  					Expect(fsVal).To(BeEquivalentTo(expectedChecksum))
   409  					Expect(val).To(BeEquivalentTo(expectedChecksum))
   410  				})
   411  
   412  				It("should accept when filesystem and memory checksums match", func() {
   413  					lom := filePut(localFQN, testFileSize)
   414  					Expect(lom.ValidateMetaChecksum()).NotTo(HaveOccurred())
   415  				})
   416  
   417  				It("should accept when both filesystem and memory checksums are nil", func() {
   418  					lom := filePut(localFQN, testFileSize)
   419  					Expect(lom.ValidateMetaChecksum()).NotTo(HaveOccurred())
   420  				})
   421  
   422  				It("should not accept when memory has wrong checksum", func() {
   423  					lom := filePut(localFQN, testFileSize)
   424  					Expect(lom.ValidateMetaChecksum()).NotTo(HaveOccurred())
   425  
   426  					lom.SetCksum(cos.NewCksum(cos.ChecksumXXHash, "wrong checksum"))
   427  					Expect(persist(lom)).NotTo(HaveOccurred())
   428  					Expect(lom.ValidateContentChecksum()).To(HaveOccurred())
   429  				})
   430  
   431  				It("should not accept when object content has changed", func() {
   432  					lom := filePut(localFQN, testFileSize)
   433  					Expect(lom.ValidateContentChecksum()).NotTo(HaveOccurred())
   434  
   435  					Expect(os.WriteFile(localFQN, []byte("wrong file"), cos.PermRWR)).To(BeNil())
   436  
   437  					Expect(lom.ValidateContentChecksum()).To(HaveOccurred())
   438  				})
   439  
   440  				It("should not check object content when recompute false", func() {
   441  					lom := filePut(localFQN, testFileSize)
   442  					Expect(lom.ValidateContentChecksum()).NotTo(HaveOccurred())
   443  
   444  					Expect(os.WriteFile(localFQN, []byte("wrong file"), cos.PermRWR)).To(BeNil())
   445  					Expect(lom.ValidateMetaChecksum()).NotTo(HaveOccurred())
   446  				})
   447  
   448  				It("should not accept when xattr has wrong checksum", func() {
   449  					lom := filePut(localFQN, testFileSize)
   450  					Expect(lom.ValidateContentChecksum()).NotTo(HaveOccurred())
   451  
   452  					lom.SetCksum(cos.NewCksum(cos.ChecksumXXHash, "wrong checksum"))
   453  					Expect(lom.ValidateMetaChecksum()).To(HaveOccurred())
   454  				})
   455  			})
   456  
   457  			// copy-paste of some of ValidateMetaChecksum tests, however if there's no
   458  			// mocking solution, it's needed to have the same tests for both methods
   459  			Describe("ValidateContentChecksum", func() {
   460  				It("should ignore if bucket checksum is none", func() {
   461  					testObject := "foldr/test-obj.ext"
   462  					noneFQN := mis[0].MakePathFQN(&localBckA, fs.ObjectType, testObject)
   463  
   464  					lom := NewBasicLom(noneFQN)
   465  					err := lom.ValidateContentChecksum()
   466  					Expect(err).NotTo(HaveOccurred())
   467  					Expect(lom.Checksum()).To(BeNil())
   468  				})
   469  
   470  				It("should fill object with checksum if was not present", func() {
   471  					lom := filePut(localFQN, testFileSize)
   472  					expectedChecksum := getTestFileHash(localFQN)
   473  
   474  					fsLOM := NewBasicLom(localFQN)
   475  					err := fsLOM.Load(false, false)
   476  					Expect(err).ShouldNot(HaveOccurred())
   477  
   478  					cksumType, _ := fsLOM.Checksum().Get()
   479  					Expect(cksumType).To(BeEquivalentTo(cos.ChecksumNone))
   480  
   481  					Expect(lom.ValidateContentChecksum()).NotTo(HaveOccurred())
   482  					lom.UncacheUnless()
   483  
   484  					Expect(lom.Checksum()).ToNot(BeNil())
   485  					_, cksumValue := lom.Checksum().Get()
   486  
   487  					fsLOM = NewBasicLom(localFQN)
   488  					err = fsLOM.Load(false, false)
   489  					Expect(err).ShouldNot(HaveOccurred())
   490  
   491  					_, fsCksmVal := fsLOM.Checksum().Get()
   492  					Expect(fsCksmVal).To(BeEquivalentTo(expectedChecksum))
   493  					Expect(cksumValue).To(BeEquivalentTo(expectedChecksum))
   494  				})
   495  
   496  				It("should accept when filesystem and memory checksums match", func() {
   497  					createTestFile(localFQN, testFileSize)
   498  					lom := NewBasicLom(localFQN)
   499  					Expect(lom.ValidateContentChecksum()).NotTo(HaveOccurred())
   500  				})
   501  
   502  				It("should accept when both filesystem and memory checksums are nil", func() {
   503  					createTestFile(localFQN, testFileSize)
   504  					lom := NewBasicLom(localFQN)
   505  
   506  					Expect(lom.ValidateContentChecksum()).NotTo(HaveOccurred())
   507  				})
   508  
   509  				It("should not accept when object content has changed", func() {
   510  					createTestFile(localFQN, testFileSize)
   511  					lom := NewBasicLom(localFQN)
   512  					Expect(lom.ValidateContentChecksum()).NotTo(HaveOccurred())
   513  
   514  					err := os.WriteFile(localFQN, []byte("wrong file"), cos.PermRWR)
   515  					Expect(err).ShouldNot(HaveOccurred())
   516  
   517  					Expect(lom.ValidateContentChecksum()).To(HaveOccurred())
   518  				})
   519  			})
   520  
   521  			Describe("FromFS", func() {
   522  				It("should error if file does not exist", func() {
   523  					testObject := "foldr/test-obj-doesnt-exist.ext"
   524  					noneFQN := mis[0].MakePathFQN(&localBckA, fs.ObjectType, testObject)
   525  					lom := NewBasicLom(noneFQN)
   526  
   527  					Expect(lom.FromFS()).To(HaveOccurred())
   528  				})
   529  
   530  				It("should fill object with correct meta", func() {
   531  					startTime := time.Now()
   532  					time.Sleep(50 * time.Millisecond)
   533  					lom1 := filePut(localFQN, testFileSize)
   534  					lom2 := NewBasicLom(localFQN)
   535  					Expect(lom1.Persist()).NotTo(HaveOccurred())
   536  
   537  					Expect(lom1.ValidateContentChecksum()).NotTo(HaveOccurred())
   538  					Expect(lom1.Persist()).ToNot(HaveOccurred())
   539  
   540  					Expect(lom2.Load(false, false)).ToNot(HaveOccurred()) // Calls `FromFS`.
   541  					Expect(lom2.Checksum()).To(BeEquivalentTo(lom1.Checksum()))
   542  					Expect(lom2.Version()).To(BeEquivalentTo(lom1.Version()))
   543  					Expect(lom2.SizeBytes()).To(BeEquivalentTo(testFileSize))
   544  					Expect(time.Unix(0, lom2.AtimeUnix()).After(startTime)).To(BeTrue())
   545  				})
   546  			})
   547  		})
   548  
   549  		Describe("Version", func() {
   550  			testObject := "foldr/test-obj.ext"
   551  			desiredVersion := "9001"
   552  			localFQN := mis[0].MakePathFQN(&localBckA, fs.ObjectType, testObject)
   553  
   554  			It("should be able to get version", func() {
   555  				lom := filePut(localFQN, 0)
   556  				lom.SetVersion(desiredVersion)
   557  				Expect(persist(lom)).NotTo(HaveOccurred())
   558  
   559  				err := lom.Load(false, false)
   560  				Expect(err).ToNot(HaveOccurred())
   561  				Expect(lom.Version()).To(BeEquivalentTo(desiredVersion))
   562  			})
   563  		})
   564  
   565  		Describe("CustomMD", func() {
   566  			testObject := "foldr/test-obj.ext"
   567  			localFQN := mis[0].MakePathFQN(&localBckA, fs.ObjectType, testObject)
   568  
   569  			It("should correctly set and get custom metadata", func() {
   570  				lom := filePut(localFQN, 0)
   571  				lom.SetCustomMD(cos.StrKVs{
   572  					cmn.SourceObjMD: apc.GCP,
   573  					cmn.ETag:        "etag",
   574  					cmn.CRC32CObjMD: "crc32",
   575  				})
   576  				value, exists := lom.GetCustomKey(cmn.SourceObjMD)
   577  				Expect(exists).To(BeTrue())
   578  				Expect(value).To(Equal(apc.GCP))
   579  				_, exists = lom.GetCustomKey("unknown")
   580  				Expect(exists).To(BeFalse())
   581  			})
   582  		})
   583  	})
   584  
   585  	Describe("copy object methods", func() {
   586  		const (
   587  			testObjectName = "foldr/test-obj.ext"
   588  			testFileSize   = 101
   589  			desiredVersion = "9002"
   590  		)
   591  
   592  		findMpath := func(objectName, bucket string, defaultLoc bool, ignoreFQNs ...string) string {
   593  		OuterLoop:
   594  			for _, mi := range mis {
   595  				bck := cmn.Bck{Name: bucket, Provider: apc.AIS, Ns: cmn.NsGlobal}
   596  				fqn := mi.MakePathFQN(&bck, fs.ObjectType, objectName)
   597  				for _, ignoreFQN := range ignoreFQNs {
   598  					if fqn == ignoreFQN {
   599  						continue OuterLoop
   600  					}
   601  				}
   602  
   603  				var parsed fs.ParsedFQN
   604  				hrw, _ := core.ResolveFQN(fqn, &parsed)
   605  				if defaultLoc && hrw == fqn {
   606  					return fqn
   607  				} else if !defaultLoc && hrw != fqn {
   608  					return fqn
   609  				}
   610  			}
   611  			cos.Assert(false)
   612  			return ""
   613  		}
   614  
   615  		mirrorFQNs := []string{
   616  			// Bucket with redundancy
   617  			findMpath(testObjectName, bucketLocalC, true /*defaultLoc*/),
   618  			findMpath(testObjectName, bucketLocalC, false /*defaultLoc*/),
   619  		}
   620  		// Add another mirrorFQN but it must be different than the second one.
   621  		mirrorFQNs = append(
   622  			mirrorFQNs,
   623  			findMpath(testObjectName, bucketLocalC, false /*defaultLoc*/, mirrorFQNs[1]),
   624  		)
   625  		// Object with different name as default one.
   626  		renamedObjFQN := findMpath("other.txt", bucketLocalC, true /*defaultLoc*/)
   627  
   628  		copyFQNs := []string{
   629  			// Bucket with no redundancy
   630  			findMpath(testObjectName, bucketLocalB, true /*defaultLoc*/),
   631  			findMpath(testObjectName, bucketLocalB, false /*defaultLoc*/),
   632  		}
   633  
   634  		prepareLOM := func(fqn string) (lom *core.LOM) {
   635  			// Prepares a basic lom with a copy
   636  			createTestFile(fqn, testFileSize)
   637  			lom = &core.LOM{}
   638  			err := lom.InitFQN(fqn, nil)
   639  
   640  			lom.SetSize(int64(testFileSize))
   641  			lom.SetVersion(desiredVersion)
   642  			Expect(persist(lom)).NotTo(HaveOccurred())
   643  			lom.UncacheUnless()
   644  			Expect(err).NotTo(HaveOccurred())
   645  			err = lom.Load(false, false)
   646  			Expect(err).NotTo(HaveOccurred())
   647  			Expect(lom.ValidateContentChecksum()).NotTo(HaveOccurred())
   648  			return
   649  		}
   650  
   651  		prepareCopy := func(lom *core.LOM, fqn string, locked ...bool) (dst *core.LOM) {
   652  			var (
   653  				err error
   654  				bck = lom.Bck()
   655  			)
   656  			if len(locked) == 0 {
   657  				lom.Lock(true)
   658  				defer lom.Unlock(true)
   659  			}
   660  			dst, err = lom.Copy2FQN(fqn, make([]byte, testFileSize))
   661  			Expect(err).ShouldNot(HaveOccurred())
   662  			Expect(dst.FQN).To(BeARegularFile())
   663  			Expect(dst.SizeBytes(true)).To(BeEquivalentTo(testFileSize))
   664  
   665  			hrwLom := &core.LOM{ObjName: lom.ObjName}
   666  			Expect(hrwLom.InitBck(bck.Bucket())).NotTo(HaveOccurred())
   667  			hrwLom.UncacheUnless()
   668  
   669  			// Reload copy, to make sure it is fresh
   670  			dst = NewBasicLom(dst.FQN)
   671  			Expect(dst.Load(false, true)).NotTo(HaveOccurred())
   672  			Expect(dst.ValidateContentChecksum()).NotTo(HaveOccurred())
   673  			hrwLom.UncacheUnless()
   674  			return
   675  		}
   676  
   677  		checkCopies := func(defaultLOM *core.LOM, copiesFQNs ...string) {
   678  			expectedHash := getTestFileHash(defaultLOM.FQN)
   679  
   680  			for _, copyFQN := range copiesFQNs {
   681  				copyLOM := NewBasicLom(copyFQN)
   682  				Expect(copyLOM.Load(false, true)).NotTo(HaveOccurred())
   683  
   684  				_, cksumValue := copyLOM.Checksum().Get()
   685  				Expect(cksumValue).To(Equal(expectedHash))
   686  				Expect(copyLOM.Version()).To(Equal(desiredVersion))
   687  				Expect(copyLOM.SizeBytes(true)).To(BeEquivalentTo(testFileSize))
   688  
   689  				Expect(copyLOM.IsCopy()).To(Equal(copyFQN != defaultLOM.FQN))
   690  				Expect(copyLOM.HasCopies()).To(BeTrue())
   691  				Expect(copyLOM.NumCopies()).To(Equal(len(copiesFQNs)))
   692  				for _, cfqn := range copiesFQNs {
   693  					Expect(copyLOM.GetCopies()).To(HaveKey(cfqn))
   694  				}
   695  
   696  				// Check that the content of the copy is correct.
   697  				copyObjHash := getTestFileHash(copyFQN)
   698  				Expect(copyObjHash).To(BeEquivalentTo(expectedHash))
   699  			}
   700  		}
   701  
   702  		Describe("CopyObject", func() {
   703  			It("should successfully copy the object", func() {
   704  				lom := prepareLOM(copyFQNs[0])
   705  				copyLOM := prepareCopy(lom, copyFQNs[1])
   706  				expectedHash := getTestFileHash(lom.FQN)
   707  
   708  				lom.Lock(false)
   709  				defer lom.Unlock(false)
   710  				// Check that no copies were added to metadata
   711  				Expect(lom.IsCopy()).To(BeFalse())
   712  				Expect(lom.HasCopies()).To(BeFalse())
   713  				Expect(lom.NumCopies()).To(Equal(1))
   714  				Expect(lom.GetCopies()).To(BeNil())
   715  
   716  				// Check copy created
   717  				Expect(copyLOM.FQN).NotTo(Equal(lom.FQN))
   718  				_, cksumValue := copyLOM.Checksum().Get()
   719  				Expect(cksumValue).To(Equal(expectedHash))
   720  				Expect(copyLOM.Version()).To(Equal(desiredVersion))
   721  				Expect(copyLOM.SizeBytes(true)).To(BeEquivalentTo(testFileSize))
   722  				Expect(copyLOM.IsCopy()).To(BeFalse())
   723  				Expect(copyLOM.HasCopies()).To(BeFalse())
   724  				Expect(copyLOM.NumCopies()).To(Equal(1))
   725  				Expect(copyLOM.GetCopies()).To(BeNil())
   726  
   727  				// Check copy contents are correct
   728  				copyObjHash := getTestFileHash(copyFQNs[1])
   729  				Expect(copyObjHash).To(BeEquivalentTo(expectedHash))
   730  			})
   731  
   732  			It("should successfully copy the object in case it is mirror copy", func() {
   733  				lom := prepareLOM(mirrorFQNs[0])
   734  				copyLOM := prepareCopy(lom, mirrorFQNs[1])
   735  				expectedHash := getTestFileHash(lom.FQN)
   736  
   737  				// Check that copies were added to metadata
   738  				lom.Lock(false)
   739  				defer lom.Unlock(false)
   740  				Expect(lom.IsCopy()).To(BeFalse())
   741  				Expect(lom.HasCopies()).To(BeTrue())
   742  				Expect(lom.NumCopies()).To(Equal(2))
   743  				Expect(lom.GetCopies()).To(And(HaveKey(mirrorFQNs[0]), HaveKey(mirrorFQNs[1])))
   744  
   745  				// Check copy created
   746  				Expect(copyLOM.FQN).NotTo(Equal(lom.FQN))
   747  				_, cksumValue := copyLOM.Checksum().Get()
   748  				Expect(cksumValue).To(Equal(expectedHash))
   749  				Expect(copyLOM.Version()).To(Equal(desiredVersion))
   750  				Expect(copyLOM.SizeBytes(true)).To(BeEquivalentTo(testFileSize))
   751  
   752  				Expect(copyLOM.IsCopy()).To(BeTrue())
   753  				Expect(copyLOM.HasCopies()).To(BeTrue())
   754  				Expect(copyLOM.NumCopies()).To(Equal(lom.NumCopies()))
   755  				Expect(copyLOM.GetCopies()).To(Equal(lom.GetCopies()))
   756  
   757  				// Check that the content of the copy is correct.
   758  				copyObjHash := getTestFileHash(mirrorFQNs[1])
   759  				Expect(copyObjHash).To(BeEquivalentTo(expectedHash))
   760  			})
   761  
   762  			It("should successfully copy the object and update metadata for other copies", func() {
   763  				lom := prepareLOM(mirrorFQNs[0])
   764  				lom.Lock(true)
   765  				defer lom.Unlock(true)
   766  				_ = prepareCopy(lom, mirrorFQNs[1], true)
   767  				_ = prepareCopy(lom, mirrorFQNs[2], true)
   768  
   769  				// Check that copies were added to metadata.
   770  				Expect(lom.IsCopy()).To(BeFalse())
   771  				Expect(lom.HasCopies()).To(BeTrue())
   772  				Expect(lom.NumCopies()).To(Equal(3))
   773  				Expect(lom.GetCopies()).To(And(HaveKey(mirrorFQNs[0]), HaveKey(mirrorFQNs[1]), HaveKey(mirrorFQNs[2])))
   774  
   775  				// Check metadata of created copies (it also checks default object).
   776  				checkCopies(lom, mirrorFQNs[0], mirrorFQNs[1], mirrorFQNs[2])
   777  			})
   778  
   779  			It("should check for missing copies during `syncMetaWithCopies`", func() {
   780  				lom := prepareLOM(mirrorFQNs[0])
   781  				lom.Lock(true)
   782  				defer lom.Unlock(true)
   783  				_ = prepareCopy(lom, mirrorFQNs[1], true)
   784  
   785  				// Check that copies were added to metadata.
   786  				Expect(lom.IsCopy()).To(BeFalse())
   787  				Expect(lom.HasCopies()).To(BeTrue())
   788  				Expect(lom.NumCopies()).To(Equal(2))
   789  				Expect(lom.GetCopies()).To(And(HaveKey(mirrorFQNs[0]), HaveKey(mirrorFQNs[1])))
   790  
   791  				// Make one copy disappear.
   792  				cos.RemoveFile(mirrorFQNs[1])
   793  
   794  				// Prepare another one (to trigger `syncMetaWithCopies`).
   795  				_ = prepareCopy(lom, mirrorFQNs[2], true)
   796  
   797  				// Check metadata of left copies (it also checks default object).
   798  				checkCopies(lom, mirrorFQNs[0], mirrorFQNs[2])
   799  			})
   800  
   801  			It("should copy object without adding it to copies if dst bucket does not support mirroring", func() {
   802  				lom := prepareLOM(mirrorFQNs[0])
   803  				_ = prepareCopy(lom, mirrorFQNs[1])
   804  				nonMirroredLOM := prepareCopy(lom, copyFQNs[0])
   805  				expectedHash := getTestFileHash(lom.FQN)
   806  
   807  				// Check that copies were added to metadata.
   808  				lom.Lock(false)
   809  				defer lom.Unlock(false)
   810  				Expect(lom.IsCopy()).To(BeFalse())
   811  				Expect(lom.HasCopies()).To(BeTrue())
   812  				Expect(lom.NumCopies()).To(Equal(2))
   813  				Expect(lom.GetCopies()).To(And(HaveKey(mirrorFQNs[0]), HaveKey(mirrorFQNs[1])))
   814  
   815  				// Check that nothing has changed in the src.
   816  				Expect(lom.IsCopy()).To(BeFalse())
   817  				Expect(lom.HasCopies()).To(BeTrue())
   818  				Expect(lom.NumCopies()).To(Equal(2))
   819  				Expect(lom.GetCopies()).To(And(HaveKey(mirrorFQNs[0]), HaveKey(mirrorFQNs[1])))
   820  
   821  				// Check destination lom.
   822  				nonMirroredLOM.Lock(false)
   823  				defer nonMirroredLOM.Unlock(false)
   824  				Expect(nonMirroredLOM.FQN).NotTo(Equal(lom.FQN))
   825  				_, cksumValue := nonMirroredLOM.Checksum().Get()
   826  				Expect(cksumValue).To(Equal(expectedHash))
   827  				Expect(nonMirroredLOM.Version()).To(Equal("1"))
   828  				Expect(nonMirroredLOM.SizeBytes(true)).To(BeEquivalentTo(testFileSize))
   829  
   830  				Expect(nonMirroredLOM.IsCopy()).To(BeFalse())
   831  				Expect(nonMirroredLOM.HasCopies()).To(BeFalse())
   832  				Expect(nonMirroredLOM.NumCopies()).To(Equal(1))
   833  				Expect(nonMirroredLOM.GetCopies()).To(BeNil())
   834  
   835  				// Check that the content of the copy is correct.
   836  				copyObjHash := getTestFileHash(nonMirroredLOM.FQN)
   837  				Expect(copyObjHash).To(BeEquivalentTo(expectedHash))
   838  			})
   839  
   840  			// This test case can happen when we rename object to some different name.
   841  			It("should not count object as mirror/copy if new object has different name", func() {
   842  				lom := prepareLOM(mirrorFQNs[0])
   843  				copyLOM := prepareCopy(lom, renamedObjFQN)
   844  				expectedHash := getTestFileHash(lom.FQN)
   845  
   846  				// Check that no copies were added to metadata.
   847  				lom.Lock(false)
   848  				defer lom.Unlock(false)
   849  				Expect(lom.IsCopy()).To(BeFalse())
   850  				Expect(lom.IsHRW()).To(BeTrue())
   851  				Expect(lom.HasCopies()).To(BeFalse())
   852  				Expect(lom.NumCopies()).To(Equal(1))
   853  				Expect(lom.GetCopies()).To(BeNil())
   854  
   855  				// Check copy created.
   856  				copyLOM.Lock(false)
   857  				defer copyLOM.Unlock(false)
   858  				Expect(copyLOM.FQN).NotTo(Equal(lom.FQN))
   859  				_, cksumValue := copyLOM.Checksum().Get()
   860  				Expect(cksumValue).To(Equal(expectedHash))
   861  				Expect(copyLOM.Version()).To(Equal(desiredVersion)) // TODO: ???
   862  				Expect(copyLOM.SizeBytes(true)).To(BeEquivalentTo(testFileSize))
   863  				Expect(copyLOM.IsHRW()).To(BeTrue())
   864  				Expect(copyLOM.IsCopy()).To(BeFalse())
   865  				Expect(copyLOM.HasCopies()).To(BeFalse())
   866  				Expect(copyLOM.NumCopies()).To(Equal(1))
   867  				Expect(copyLOM.GetCopies()).To(BeNil())
   868  			})
   869  
   870  			// This test case can happen when user adds new mountpath and all existing
   871  			// copies become non-HRW and now we need to create a HRW object.
   872  			It("should copy object if mirroring the non-HRW object to HRW object", func() {
   873  				copyLOM := prepareLOM(mirrorFQNs[1])
   874  				// Recreate main/HRW object from the copy.
   875  				lom := prepareCopy(copyLOM, mirrorFQNs[0])
   876  
   877  				// Check that HRW object was created correctly.
   878  				lom.Lock(false)
   879  				defer lom.Unlock(false)
   880  				Expect(lom.IsCopy()).To(BeFalse())
   881  				Expect(lom.HasCopies()).To(BeTrue())
   882  				Expect(lom.NumCopies()).To(Equal(2))
   883  				Expect(lom.GetCopies()).To(And(HaveKey(mirrorFQNs[0]), HaveKey(mirrorFQNs[1])))
   884  				Expect(lom.Version()).To(Equal(desiredVersion))
   885  				Expect(lom.SizeBytes(true)).To(BeEquivalentTo(testFileSize))
   886  
   887  				// Check that copy from which HRW object was created is also updated.
   888  				Expect(copyLOM.FQN).NotTo(Equal(lom.FQN))
   889  				Expect(copyLOM.Version()).To(Equal(desiredVersion))
   890  				Expect(copyLOM.SizeBytes(true)).To(BeEquivalentTo(testFileSize))
   891  				Expect(copyLOM.IsCopy()).To(BeTrue())
   892  				Expect(copyLOM.HasCopies()).To(BeTrue())
   893  				Expect(copyLOM.NumCopies()).To(Equal(lom.NumCopies()))
   894  				Expect(copyLOM.GetCopies()).To(Equal(lom.GetCopies()))
   895  			})
   896  		})
   897  
   898  		Describe("DelCopies", func() {
   899  			It("should delete mirrored copy", func() {
   900  				lom := prepareLOM(mirrorFQNs[0])
   901  				_ = prepareCopy(lom, mirrorFQNs[1])
   902  
   903  				// Check that no copies were added to metadata.
   904  				lom.Lock(false)
   905  				defer lom.Unlock(false)
   906  				Expect(lom.IsCopy()).To(BeFalse())
   907  				Expect(lom.HasCopies()).To(BeTrue())
   908  				Expect(lom.NumCopies()).To(Equal(2))
   909  				Expect(lom.GetCopies()).To(And(HaveKey(mirrorFQNs[0]), HaveKey(mirrorFQNs[1])))
   910  
   911  				// Delete copy and check if it's gone.
   912  				Expect(lom.DelCopies(mirrorFQNs[1])).ToNot(HaveOccurred())
   913  				Expect(persist(lom)).ToNot(HaveOccurred())
   914  				Expect(mirrorFQNs[1]).NotTo(BeAnExistingFile())
   915  
   916  				// Reload default object and check if the lom was correctly updated.
   917  				lom = NewBasicLom(mirrorFQNs[0])
   918  				Expect(lom.Load(false, true)).ToNot(HaveOccurred())
   919  				Expect(lom.IsCopy()).To(BeFalse())
   920  				Expect(lom.HasCopies()).To(BeFalse())
   921  				Expect(lom.NumCopies()).To(Equal(1))
   922  				Expect(lom.GetCopies()).To(BeNil())
   923  			})
   924  
   925  			It("should delete mirrored copy and update other copies", func() {
   926  				lom := prepareLOM(mirrorFQNs[0])
   927  				_ = prepareCopy(lom, mirrorFQNs[1])
   928  				_ = prepareCopy(lom, mirrorFQNs[2])
   929  				expectedHash := getTestFileHash(lom.FQN)
   930  
   931  				// Check that copies were added to metadata.
   932  				lom.Lock(true)
   933  				defer lom.Unlock(true)
   934  				Expect(lom.IsCopy()).To(BeFalse())
   935  				Expect(lom.HasCopies()).To(BeTrue())
   936  				Expect(lom.NumCopies()).To(Equal(3))
   937  				Expect(lom.GetCopies()).To(And(HaveKey(mirrorFQNs[0]), HaveKey(mirrorFQNs[1]), HaveKey(mirrorFQNs[2])))
   938  
   939  				// Delete copy and check if it's gone.
   940  				Expect(lom.DelCopies(mirrorFQNs[1])).ToNot(HaveOccurred())
   941  				Expect(persist(lom)).ToNot(HaveOccurred())
   942  				Expect(mirrorFQNs[1]).NotTo(BeAnExistingFile())
   943  
   944  				// Reload default object and check if the lom was correctly updated.
   945  				lom = NewBasicLom(mirrorFQNs[0])
   946  				Expect(lom.Load(false, true)).ToNot(HaveOccurred())
   947  				Expect(lom.IsCopy()).To(BeFalse())
   948  				Expect(lom.HasCopies()).To(BeTrue())
   949  				Expect(lom.NumCopies()).To(Equal(2))
   950  				Expect(lom.GetCopies()).To(And(HaveKey(mirrorFQNs[0]), HaveKey(mirrorFQNs[2])))
   951  
   952  				// Check that left copy was correctly updated.
   953  				copyLOM := NewBasicLom(mirrorFQNs[2])
   954  				Expect(copyLOM.Load(false, true)).NotTo(HaveOccurred())
   955  				_, cksumValue := copyLOM.Checksum().Get()
   956  				Expect(cksumValue).To(Equal(expectedHash))
   957  				Expect(copyLOM.Version()).To(Equal(desiredVersion))
   958  				Expect(copyLOM.SizeBytes()).To(BeEquivalentTo(testFileSize))
   959  
   960  				Expect(copyLOM.IsCopy()).To(BeTrue())
   961  				Expect(copyLOM.HasCopies()).To(BeTrue())
   962  				Expect(copyLOM.NumCopies()).To(Equal(lom.NumCopies()))
   963  				Expect(copyLOM.GetCopies()).To(Equal(lom.GetCopies()))
   964  			})
   965  		})
   966  
   967  		Describe("DelAllCopies", func() {
   968  			It("should be able to delete all copies", func() {
   969  				lom := prepareLOM(mirrorFQNs[0])
   970  				_ = prepareCopy(lom, mirrorFQNs[1])
   971  				_ = prepareCopy(lom, mirrorFQNs[2])
   972  
   973  				// Sanity check for default object.
   974  				lom.Lock(false)
   975  				defer lom.Unlock(false)
   976  				Expect(lom.IsCopy()).To(BeFalse())
   977  				Expect(lom.HasCopies()).To(BeTrue())
   978  				Expect(lom.NumCopies()).To(Equal(3))
   979  				Expect(lom.GetCopies()).To(And(HaveKey(mirrorFQNs[0]), HaveKey(mirrorFQNs[1]), HaveKey(mirrorFQNs[2])))
   980  
   981  				// Delete all copies and check if they are gone.
   982  				Expect(lom.DelAllCopies()).NotTo(HaveOccurred())
   983  				Expect(persist(lom)).ToNot(HaveOccurred())
   984  				Expect(mirrorFQNs[1]).NotTo(BeAnExistingFile())
   985  				Expect(mirrorFQNs[2]).NotTo(BeAnExistingFile())
   986  
   987  				// Reload default object and see if the lom was correctly updated.
   988  				lom = NewBasicLom(mirrorFQNs[0])
   989  				Expect(lom.Load(false, true)).ToNot(HaveOccurred())
   990  				Expect(lom.IsCopy()).To(BeFalse())
   991  				Expect(lom.HasCopies()).To(BeFalse())
   992  				Expect(lom.NumCopies()).To(Equal(1))
   993  				Expect(lom.GetCopies()).To(BeNil())
   994  			})
   995  		})
   996  	})
   997  
   998  	Describe("local and cloud bucket with the same name", func() {
   999  		It("should have different fqn", func() {
  1000  			testObject := "foldr/test-obj.ext"
  1001  			localSameBck := cmn.Bck{Name: sameBucketName, Provider: apc.AIS, Ns: cmn.NsGlobal}
  1002  			cloudSameBck := cmn.Bck{Name: sameBucketName, Provider: apc.AWS, Ns: cmn.NsGlobal}
  1003  			desiredLocalFQN := mis[0].MakePathFQN(&localSameBck, fs.ObjectType, testObject)
  1004  			desiredCloudFQN := mis[0].MakePathFQN(&cloudSameBck, fs.ObjectType, testObject)
  1005  
  1006  			fs.Disable(mpaths[1]) // Ensure that it matches desiredCloudFQN
  1007  			fs.Disable(mpaths[2]) // ditto
  1008  
  1009  			lomLocal := &core.LOM{ObjName: testObject}
  1010  			err := lomLocal.InitBck(&cmn.Bck{Name: sameBucketName, Provider: apc.AIS})
  1011  			Expect(err).NotTo(HaveOccurred())
  1012  			err = lomLocal.Load(false, false)
  1013  			Expect(cos.IsNotExist(err, 0)).To(BeTrue())
  1014  			Expect(lomLocal.FQN).To(Equal(desiredLocalFQN))
  1015  			Expect(lomLocal.Uname()).To(Equal(lomLocal.Bck().MakeUname(testObject)))
  1016  			Expect(lomLocal.Bck().Provider).To(Equal(apc.AIS))
  1017  			Expect(lomLocal.Mountpath().Path).To(Equal(mpaths[0]))
  1018  			expectEqualBck(lomLocal.Bucket(), &localSameBck)
  1019  			Expect(lomLocal.ObjName).To(Equal(testObject))
  1020  
  1021  			lomCloud := &core.LOM{ObjName: testObject}
  1022  			err = lomCloud.InitBck(&cmn.Bck{Name: sameBucketName, Provider: apc.AWS})
  1023  			Expect(err).NotTo(HaveOccurred())
  1024  			err = lomCloud.Load(false, false)
  1025  			Expect(cos.IsNotExist(err, 0)).To(BeTrue())
  1026  			Expect(lomCloud.FQN).To(Equal(desiredCloudFQN))
  1027  			Expect(lomCloud.Uname()).To(Equal(lomCloud.Bck().MakeUname(testObject)))
  1028  			Expect(lomCloud.Bck().Provider).To(Equal(apc.AWS))
  1029  			Expect(lomCloud.Mountpath().Path).To(Equal(mpaths[0]))
  1030  			expectEqualBck(lomCloud.Bucket(), &cloudSameBck)
  1031  			Expect(lomCloud.ObjName).To(Equal(testObject))
  1032  
  1033  			fs.Enable(mpaths[1])
  1034  			fs.Enable(mpaths[2])
  1035  		})
  1036  	})
  1037  })
  1038  
  1039  //
  1040  // HELPERS
  1041  //
  1042  
  1043  // needs to be called inside of gomega scope like Describe/It
  1044  func NewBasicLom(fqn string) *core.LOM {
  1045  	lom := &core.LOM{}
  1046  	err := lom.InitFQN(fqn, nil)
  1047  	Expect(err).NotTo(HaveOccurred())
  1048  	return lom
  1049  }
  1050  
  1051  func filePut(fqn string, size int) *core.LOM {
  1052  	createTestFile(fqn, size)
  1053  	lom := NewBasicLom(fqn)
  1054  	lom.SetSize(int64(size))
  1055  	lom.IncVersion()
  1056  	Expect(persist(lom)).NotTo(HaveOccurred())
  1057  	lom.UncacheUnless()
  1058  	return lom
  1059  }
  1060  
  1061  func createTestFile(fqn string, size int) {
  1062  	_ = os.Remove(fqn)
  1063  	testFile, err := cos.CreateFile(fqn)
  1064  	Expect(err).ShouldNot(HaveOccurred())
  1065  
  1066  	if size > 0 {
  1067  		buff := make([]byte, size)
  1068  		_, _ = cryptorand.Read(buff)
  1069  		_, err := testFile.Write(buff)
  1070  		_ = testFile.Close()
  1071  
  1072  		Expect(err).ShouldNot(HaveOccurred())
  1073  	}
  1074  }
  1075  
  1076  func getTestFileHash(fqn string) (hash string) {
  1077  	reader, _ := os.Open(fqn)
  1078  	_, cksum, err := cos.CopyAndChecksum(io.Discard, reader, nil, cos.ChecksumXXHash)
  1079  	Expect(err).NotTo(HaveOccurred())
  1080  	hash = cksum.Value()
  1081  	reader.Close()
  1082  	return
  1083  }
  1084  
  1085  func expectEqualBck(left, right *cmn.Bck) {
  1086  	p := right.Props
  1087  	right.Props = left.Props
  1088  	_ = Expect(left).To(Equal(right))
  1089  	right.Props = p
  1090  }
  1091  
  1092  func persist(lom *core.LOM) error {
  1093  	if lom.AtimeUnix() == 0 {
  1094  		lom.SetAtimeUnix(time.Now().UnixNano())
  1095  	}
  1096  	return lom.Persist()
  1097  }