github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/test/object_test.go (about)

     1  // Package integration_test.
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package integration_test
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/hex"
    10  	"fmt"
    11  	"io"
    12  	"math/rand"
    13  	"net/http"
    14  	"net/url"
    15  	"os"
    16  	"reflect"
    17  	"strconv"
    18  	"strings"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/NVIDIA/aistore/api"
    23  	"github.com/NVIDIA/aistore/api/apc"
    24  	"github.com/NVIDIA/aistore/cmn"
    25  	"github.com/NVIDIA/aistore/cmn/cos"
    26  	"github.com/NVIDIA/aistore/core/meta"
    27  	"github.com/NVIDIA/aistore/core/mock"
    28  	"github.com/NVIDIA/aistore/tools"
    29  	"github.com/NVIDIA/aistore/tools/docker"
    30  	"github.com/NVIDIA/aistore/tools/readers"
    31  	"github.com/NVIDIA/aistore/tools/tassert"
    32  	"github.com/NVIDIA/aistore/tools/tlog"
    33  	"github.com/NVIDIA/aistore/tools/trand"
    34  	"github.com/NVIDIA/aistore/xact"
    35  )
    36  
    37  const (
    38  	httpBucketURL           = "http://storage.googleapis.com/minikube/"
    39  	httpObjectName          = "minikube-0.6.iso.sha256"
    40  	httpObjectURL           = httpBucketURL + httpObjectName
    41  	httpAnotherObjectName   = "minikube-0.7.iso.sha256"
    42  	httpAnotherObjectURL    = httpBucketURL + httpAnotherObjectName
    43  	httpObjectOutput        = "ff0f444f4a01f0ec7925e6bb0cb05e84156cff9cc8de6d03102d8b3df35693e2"
    44  	httpAnotherObjectOutput = "aadc8b6f5720d5a493a36e1f07f71bffb588780c76498d68cd761793d2ca344e"
    45  )
    46  
    47  const (
    48  	getOP = "GET"
    49  	putOP = "PUT"
    50  )
    51  
    52  func TestObjectInvalidName(t *testing.T) {
    53  	var (
    54  		proxyURL   = tools.RandomProxyURL(t)
    55  		baseParams = tools.BaseAPIParams()
    56  		bck        = cmn.Bck{
    57  			Name:     trand.String(10),
    58  			Provider: apc.AIS,
    59  		}
    60  	)
    61  
    62  	tests := []struct {
    63  		op      string
    64  		objName string
    65  	}{
    66  		{op: putOP, objName: "."},
    67  		{op: putOP, objName: ".."},
    68  		{op: putOP, objName: "../smth.txt"},
    69  		{op: putOP, objName: "../."},
    70  		{op: putOP, objName: "../.."},
    71  		{op: putOP, objName: "///"},
    72  		{op: putOP, objName: ""},
    73  		{op: putOP, objName: "1\x00"},
    74  		{op: putOP, objName: "\n"},
    75  
    76  		{op: getOP, objName: ""},
    77  		{op: getOP, objName: ".."},
    78  		{op: getOP, objName: "///"},
    79  		{op: getOP, objName: "...../log/aisnode.INFO"},
    80  		{op: getOP, objName: "/...../log/aisnode.INFO"},
    81  		{op: getOP, objName: "/\\../\\../\\../\\../log/aisnode.INFO"},
    82  		{op: getOP, objName: "\\../\\../\\../\\../log/aisnode.INFO"},
    83  		{op: getOP, objName: "/../../../../log/aisnode.INFO"},
    84  		{op: getOP, objName: "/././../../../../log/aisnode.INFO"},
    85  	}
    86  
    87  	tools.CreateBucket(t, proxyURL, bck, nil, true /*cleanup*/)
    88  
    89  	for _, test := range tests {
    90  		t.Run(test.op, func(t *testing.T) {
    91  			switch test.op {
    92  			case putOP:
    93  				reader, err := readers.NewRand(cos.KiB, cos.ChecksumNone)
    94  				tassert.CheckFatal(t, err)
    95  				_, err = api.PutObject(&api.PutArgs{
    96  					BaseParams: baseParams,
    97  					Bck:        bck,
    98  					ObjName:    test.objName,
    99  					Reader:     reader,
   100  				})
   101  				tassert.Errorf(t, err != nil, "expected error to occur (object name: %q)", test.objName)
   102  			case getOP:
   103  				_, err := api.GetObjectWithValidation(baseParams, bck, test.objName, nil)
   104  				tassert.Errorf(t, err != nil, "expected error to occur (object name: %q)", test.objName)
   105  			default:
   106  				panic(test.op)
   107  			}
   108  		})
   109  	}
   110  }
   111  
   112  func TestRemoteBucketObject(t *testing.T) {
   113  	var (
   114  		baseParams = tools.BaseAPIParams()
   115  		bck        = cliBck
   116  	)
   117  
   118  	tools.CheckSkip(t, &tools.SkipTestArgs{Long: true, RemoteBck: true, Bck: bck})
   119  
   120  	tests := []struct {
   121  		ty     string
   122  		exists bool
   123  	}{
   124  		{putOP, false},
   125  		{putOP, true},
   126  		{getOP, false},
   127  		{getOP, true},
   128  	}
   129  
   130  	for _, test := range tests {
   131  		t.Run(fmt.Sprintf("%s:%v", test.ty, test.exists), func(t *testing.T) {
   132  			object := trand.String(10)
   133  			if !test.exists {
   134  				bck.Name = trand.String(10)
   135  			} else {
   136  				bck.Name = cliBck.Name
   137  			}
   138  
   139  			reader, err := readers.NewRand(cos.KiB, cos.ChecksumNone)
   140  			tassert.CheckFatal(t, err)
   141  
   142  			defer api.DeleteObject(baseParams, bck, object)
   143  
   144  			switch test.ty {
   145  			case putOP:
   146  				var oah api.ObjAttrs
   147  				oah, err = api.PutObject(&api.PutArgs{
   148  					BaseParams: baseParams,
   149  					Bck:        bck,
   150  					ObjName:    object,
   151  					Reader:     reader,
   152  				})
   153  				if err == nil {
   154  					oa := oah.Attrs()
   155  					tlog.Logf("PUT(%s) attrs %s\n", bck.Cname(object), oa.String())
   156  				}
   157  			case getOP:
   158  				if test.exists {
   159  					_, err = api.PutObject(&api.PutArgs{
   160  						BaseParams: baseParams,
   161  						Bck:        bck,
   162  						ObjName:    object,
   163  						Reader:     reader,
   164  					})
   165  					tassert.CheckFatal(t, err)
   166  				}
   167  
   168  				_, err = api.GetObjectWithValidation(baseParams, bck, object, nil)
   169  			default:
   170  				t.Fail()
   171  			}
   172  
   173  			if !test.exists {
   174  				if err == nil {
   175  					t.Errorf("expected error when doing %s on non existing %q bucket", test.ty, bck)
   176  				}
   177  			} else if err != nil {
   178  				t.Errorf("expected no error when executing %s on existing %q bucket(err = %v)",
   179  					test.ty, bck, err)
   180  			}
   181  		})
   182  	}
   183  }
   184  
   185  func TestHttpProviderObjectGet(t *testing.T) {
   186  	var (
   187  		proxyURL   = tools.RandomProxyURL()
   188  		baseParams = tools.BaseAPIParams(proxyURL)
   189  		hbo, _     = cmn.NewHTTPObjPath(httpObjectURL)
   190  		w          = bytes.NewBuffer(nil)
   191  		getArgs    = api.GetArgs{Writer: w}
   192  	)
   193  	t.Cleanup(func() {
   194  		tools.DestroyBucket(t, proxyURL, hbo.Bck)
   195  	})
   196  
   197  	// get using the HTTP API
   198  	getArgs.Query = make(url.Values, 1)
   199  	getArgs.Query.Set(apc.QparamOrigURL, httpObjectURL)
   200  	_, err := api.GetObject(baseParams, hbo.Bck, httpObjectName, &getArgs)
   201  	tassert.CheckFatal(t, err)
   202  	tassert.Fatalf(t, strings.TrimSpace(w.String()) == httpObjectOutput, "bad content (expected:%s got:%s)",
   203  		httpObjectOutput, w.String())
   204  
   205  	// get another object using /v1/objects/bucket-name/object-name endpoint
   206  	w.Reset()
   207  	getArgs.Query = make(url.Values, 1)
   208  	getArgs.Query.Set(apc.QparamOrigURL, httpAnotherObjectURL)
   209  	_, err = api.GetObject(baseParams, hbo.Bck, httpAnotherObjectName, &getArgs)
   210  	tassert.CheckFatal(t, err)
   211  	tassert.Fatalf(t, strings.TrimSpace(w.String()) == httpAnotherObjectOutput, "bad content (expected:%s got:%s)",
   212  		httpAnotherObjectOutput, w.String())
   213  
   214  	// list object should contain both the objects
   215  	reslist, err := api.ListObjects(baseParams, hbo.Bck, &apc.LsoMsg{}, api.ListArgs{})
   216  	tassert.CheckFatal(t, err)
   217  	tassert.Errorf(t, len(reslist.Entries) == 2, "should have exactly 2 entries in bucket")
   218  
   219  	matchCount := 0
   220  	for _, en := range reslist.Entries {
   221  		if en.Name == httpAnotherObjectName || en.Name == httpObjectName {
   222  			matchCount++
   223  		}
   224  	}
   225  	tassert.Errorf(t, matchCount == 2, "objects %s and %s should be present in %s",
   226  		httpObjectName, httpAnotherObjectName, hbo.Bck)
   227  }
   228  
   229  func TestAppendObject(t *testing.T) {
   230  	for _, cksumType := range cos.SupportedChecksums() {
   231  		t.Run(cksumType, func(t *testing.T) {
   232  			var (
   233  				proxyURL   = tools.RandomProxyURL(t)
   234  				baseParams = tools.BaseAPIParams(proxyURL)
   235  				bck        = cmn.Bck{
   236  					Name:     trand.String(10),
   237  					Provider: apc.AIS,
   238  				}
   239  				objName = "test/obj1"
   240  
   241  				objHead = "1111111111"
   242  				objBody = "222222222222222"
   243  				objTail = "333333333"
   244  				content = objHead + objBody + objTail
   245  				objSize = len(content)
   246  			)
   247  			tools.CreateBucket(t, proxyURL, bck,
   248  				&cmn.BpropsToSet{Cksum: &cmn.CksumConfToSet{Type: apc.Ptr(cksumType)}},
   249  				true, /*cleanup*/
   250  			)
   251  
   252  			var (
   253  				err    error
   254  				handle string
   255  				cksum  = cos.NewCksumHash(cksumType)
   256  			)
   257  			for _, body := range []string{objHead, objBody, objTail} {
   258  				args := api.AppendArgs{
   259  					BaseParams: baseParams,
   260  					Bck:        bck,
   261  					Object:     objName,
   262  					Handle:     handle,
   263  					Reader:     cos.NewByteHandle([]byte(body)),
   264  				}
   265  				handle, err = api.AppendObject(&args)
   266  				tassert.CheckFatal(t, err)
   267  
   268  				_, err = cksum.H.Write([]byte(body))
   269  				tassert.CheckFatal(t, err)
   270  			}
   271  
   272  			// Flush object with cksum to make it persistent in the bucket.
   273  			cksum.Finalize()
   274  			err = api.FlushObject(&api.FlushArgs{
   275  				BaseParams: baseParams,
   276  				Bck:        bck,
   277  				Object:     objName,
   278  				Handle:     handle,
   279  				Cksum:      cksum.Clone(),
   280  			})
   281  			tassert.CheckFatal(t, err)
   282  
   283  			// Read the object from the bucket.
   284  			writer := bytes.NewBuffer(nil)
   285  			getArgs := api.GetArgs{Writer: writer}
   286  			oah, err := api.GetObjectWithValidation(baseParams, bck, objName, &getArgs)
   287  			if !cksum.IsEmpty() {
   288  				tassert.CheckFatal(t, err)
   289  			}
   290  			tassert.Errorf(
   291  				t, writer.String() == content,
   292  				"invalid object content: [%d](%s), expected: [%d](%s)",
   293  				oah.Size(), writer.String(), objSize, content,
   294  			)
   295  		})
   296  		time.Sleep(3 * time.Second)
   297  	}
   298  }
   299  
   300  func TestSameBucketName(t *testing.T) {
   301  	var (
   302  		proxyURL   = tools.RandomProxyURL(t)
   303  		baseParams = tools.BaseAPIParams(proxyURL)
   304  		bckLocal   = cmn.Bck{
   305  			Name:     cliBck.Name,
   306  			Provider: apc.AIS,
   307  		}
   308  		bckRemote  = cliBck
   309  		fileName1  = "mytestobj1.txt"
   310  		fileName2  = "mytestobj2.txt"
   311  		objRange   = "mytestobj{1..2}.txt"
   312  		dataLocal  = []byte("I'm from ais:// bucket")
   313  		dataRemote = []byte("I'm from the cloud!")
   314  		files      = []string{fileName1, fileName2}
   315  	)
   316  
   317  	tools.CheckSkip(t, &tools.SkipTestArgs{RemoteBck: true, Bck: bckRemote})
   318  
   319  	putArgsLocal := api.PutArgs{
   320  		BaseParams: baseParams,
   321  		Bck:        bckLocal,
   322  		ObjName:    fileName1,
   323  		Reader:     readers.NewBytes(dataLocal),
   324  	}
   325  
   326  	putArgsRemote := api.PutArgs{
   327  		BaseParams: baseParams,
   328  		Bck:        bckRemote,
   329  		ObjName:    fileName1,
   330  		Reader:     readers.NewBytes(dataRemote),
   331  	}
   332  
   333  	// PUT/GET/DEL Without ais bucket
   334  	tlog.Logf("Validating responses for non-existent ais bucket...\n")
   335  	_, err := api.PutObject(&putArgsLocal)
   336  	if err == nil {
   337  		t.Fatalf("ais bucket %s does not exist: Expected an error.", bckLocal.String())
   338  	}
   339  
   340  	// PUT -> remote
   341  	_, err = api.PutObject(&putArgsRemote)
   342  	tassert.CheckFatal(t, err)
   343  	putArgsRemote.ObjName = fileName2
   344  	_, err = api.PutObject(&putArgsRemote)
   345  	tassert.CheckFatal(t, err)
   346  	putArgsRemote.ObjName = fileName1
   347  
   348  	_, err = api.GetObject(baseParams, bckLocal, fileName1, nil)
   349  	if err == nil {
   350  		t.Fatalf("ais bucket %s does not exist: Expected an error.", bckLocal.String())
   351  	}
   352  
   353  	err = api.DeleteObject(baseParams, bckLocal, fileName1)
   354  	if err == nil {
   355  		t.Fatalf("ais bucket %s does not exist: Expected an error.", bckLocal.String())
   356  	}
   357  
   358  	tlog.Logf("PrefetchList num=%d\n", len(files))
   359  	{
   360  		var msg apc.PrefetchMsg
   361  		msg.ObjNames = files
   362  		prefetchListID, err := api.Prefetch(baseParams, bckRemote, msg)
   363  		tassert.CheckFatal(t, err)
   364  		args := xact.ArgsMsg{ID: prefetchListID, Kind: apc.ActPrefetchObjects, Timeout: tools.RebalanceTimeout}
   365  		_, err = api.WaitForXactionIC(baseParams, &args)
   366  		tassert.CheckFatal(t, err)
   367  	}
   368  
   369  	tlog.Logf("PrefetchRange %s\n", objRange)
   370  	{
   371  		var msg apc.PrefetchMsg
   372  		msg.Template = objRange
   373  		prefetchRangeID, err := api.Prefetch(baseParams, bckRemote, msg)
   374  		tassert.CheckFatal(t, err)
   375  		args := xact.ArgsMsg{ID: prefetchRangeID, Kind: apc.ActPrefetchObjects, Timeout: tools.RebalanceTimeout}
   376  		_, err = api.WaitForXactionIC(baseParams, &args)
   377  		tassert.CheckFatal(t, err)
   378  	}
   379  
   380  	// delete one obj from remote, and check evictions (below)
   381  	err = api.DeleteObject(baseParams, bckRemote, fileName1)
   382  	tassert.CheckFatal(t, err)
   383  
   384  	tlog.Logf("EvictList %v\n", files)
   385  	evictListID, err := api.EvictMultiObj(baseParams, bckRemote, files, "" /*template*/)
   386  	tassert.CheckFatal(t, err)
   387  	args := xact.ArgsMsg{ID: evictListID, Kind: apc.ActEvictObjects, Timeout: tools.RebalanceTimeout}
   388  	status, err := api.WaitForXactionIC(baseParams, &args)
   389  	tassert.CheckFatal(t, err)
   390  	tassert.Errorf(t, status.ErrMsg != "", "expecting errors when not finding listed objects")
   391  
   392  	tlog.Logf("EvictRange\n")
   393  	evictRangeID, err := api.EvictMultiObj(baseParams, bckRemote, nil /*lst objnames*/, objRange)
   394  	tassert.CheckFatal(t, err)
   395  	args = xact.ArgsMsg{ID: evictRangeID, Kind: apc.ActEvictObjects, Timeout: tools.RebalanceTimeout}
   396  	_, err = api.WaitForXactionIC(baseParams, &args)
   397  	tassert.CheckFatal(t, err)
   398  
   399  	tools.CreateBucket(t, proxyURL, bckLocal, nil, true /*cleanup*/)
   400  
   401  	// PUT
   402  	tlog.Logf("PUT %s and %s -> both buckets...\n", fileName1, fileName2)
   403  	_, err = api.PutObject(&putArgsLocal)
   404  	tassert.CheckFatal(t, err)
   405  	putArgsLocal.ObjName = fileName2
   406  	_, err = api.PutObject(&putArgsLocal)
   407  	tassert.CheckFatal(t, err)
   408  
   409  	_, err = api.PutObject(&putArgsRemote)
   410  	tassert.CheckFatal(t, err)
   411  	putArgsRemote.ObjName = fileName2
   412  	_, err = api.PutObject(&putArgsRemote)
   413  	tassert.CheckFatal(t, err)
   414  
   415  	// Check that ais bucket has 2 objects
   416  	tlog.Logf("Validating that ais bucket contains %s and %s ...\n", fileName1, fileName2)
   417  	_, err = api.HeadObject(baseParams, bckLocal, fileName1, apc.FltPresent, false /*silent*/)
   418  	tassert.CheckFatal(t, err)
   419  	_, err = api.HeadObject(baseParams, bckLocal, fileName2, apc.FltPresent, false /*silent*/)
   420  	tassert.CheckFatal(t, err)
   421  
   422  	// Prefetch/Evict should work
   423  	{
   424  		var msg apc.PrefetchMsg
   425  		msg.ObjNames = files
   426  		prefetchListID, err := api.Prefetch(baseParams, bckRemote, msg)
   427  		tassert.CheckFatal(t, err)
   428  		args = xact.ArgsMsg{ID: prefetchListID, Kind: apc.ActPrefetchObjects, Timeout: tools.RebalanceTimeout}
   429  		_, err = api.WaitForXactionIC(baseParams, &args)
   430  		tassert.CheckFatal(t, err)
   431  	}
   432  
   433  	evictListID, err = api.EvictMultiObj(baseParams, bckRemote, files, "" /*template*/)
   434  	tassert.CheckFatal(t, err)
   435  	args = xact.ArgsMsg{ID: evictListID, Kind: apc.ActEvictObjects, Timeout: tools.RebalanceTimeout}
   436  	_, err = api.WaitForXactionIC(baseParams, &args)
   437  	tassert.CheckFatal(t, err)
   438  
   439  	// Delete from cloud bucket
   440  	tlog.Logf("Deleting %s and %s from cloud bucket ...\n", fileName1, fileName2)
   441  	deleteID, err := api.DeleteMultiObj(baseParams, bckRemote, files, "" /*template*/)
   442  	tassert.CheckFatal(t, err)
   443  	args = xact.ArgsMsg{ID: deleteID, Kind: apc.ActDeleteObjects, Timeout: tools.RebalanceTimeout}
   444  	_, err = api.WaitForXactionIC(baseParams, &args)
   445  	tassert.CheckFatal(t, err)
   446  
   447  	// Delete from ais bucket
   448  	tlog.Logf("Deleting %s and %s from ais bucket ...\n", fileName1, fileName2)
   449  	deleteID, err = api.DeleteMultiObj(baseParams, bckLocal, files, "" /*template*/)
   450  	tassert.CheckFatal(t, err)
   451  	args = xact.ArgsMsg{ID: deleteID, Kind: apc.ActDeleteObjects, Timeout: tools.RebalanceTimeout}
   452  	_, err = api.WaitForXactionIC(baseParams, &args)
   453  	tassert.CheckFatal(t, err)
   454  
   455  	_, err = api.HeadObject(baseParams, bckLocal, fileName1, apc.FltPresent, false /*silent*/)
   456  	if err == nil || !strings.Contains(err.Error(), strconv.Itoa(http.StatusNotFound)) {
   457  		t.Errorf("Local file %s not deleted", fileName1)
   458  	}
   459  	_, err = api.HeadObject(baseParams, bckLocal, fileName2, apc.FltPresent, false /*silent*/)
   460  	if err == nil || !strings.Contains(err.Error(), strconv.Itoa(http.StatusNotFound)) {
   461  		t.Errorf("Local file %s not deleted", fileName2)
   462  	}
   463  
   464  	_, err = api.HeadObject(baseParams, bckRemote, fileName1, apc.FltExists, false /*silent*/)
   465  	if err == nil {
   466  		t.Errorf("remote file %s not deleted", fileName1)
   467  	}
   468  	_, err = api.HeadObject(baseParams, bckRemote, fileName2, apc.FltExists, false /*silent*/)
   469  	if err == nil {
   470  		t.Errorf("remote file %s not deleted", fileName2)
   471  	}
   472  }
   473  
   474  func Test_SameAISAndRemoteBucketName(t *testing.T) {
   475  	var (
   476  		defLocalProps  cmn.BpropsToSet
   477  		defRemoteProps cmn.BpropsToSet
   478  
   479  		bckLocal = cmn.Bck{
   480  			Name:     cliBck.Name,
   481  			Provider: apc.AIS,
   482  		}
   483  		bckRemote  = cliBck
   484  		proxyURL   = tools.RandomProxyURL(t)
   485  		baseParams = tools.BaseAPIParams(proxyURL)
   486  		fileName   = "mytestobj1.txt"
   487  		dataLocal  = []byte("im local")
   488  		dataRemote = []byte("I'm from the cloud!")
   489  		msg        = &apc.LsoMsg{Props: "size,status", Prefix: "my"}
   490  		found      = false
   491  	)
   492  
   493  	tools.CheckSkip(t, &tools.SkipTestArgs{RemoteBck: true, Bck: bckRemote})
   494  
   495  	tools.CreateBucket(t, proxyURL, bckLocal, nil, true /*cleanup*/)
   496  
   497  	bucketPropsLocal := &cmn.BpropsToSet{
   498  		Cksum: &cmn.CksumConfToSet{
   499  			Type: apc.Ptr(cos.ChecksumNone),
   500  		},
   501  	}
   502  	bucketPropsRemote := &cmn.BpropsToSet{}
   503  
   504  	// Put
   505  	tlog.Logf("PUT %s => %s\n", fileName, bckLocal)
   506  	putArgs := api.PutArgs{
   507  		BaseParams: baseParams,
   508  		Bck:        bckLocal,
   509  		ObjName:    fileName,
   510  		Reader:     readers.NewBytes(dataLocal),
   511  	}
   512  	_, err := api.PutObject(&putArgs)
   513  	tassert.CheckFatal(t, err)
   514  
   515  	resLocal, err := api.ListObjects(baseParams, bckLocal, msg, api.ListArgs{})
   516  	tassert.CheckFatal(t, err)
   517  
   518  	tlog.Logf("PUT %s => %s\n", fileName, bckRemote)
   519  	putArgs = api.PutArgs{
   520  		BaseParams: baseParams,
   521  		Bck:        bckRemote,
   522  		ObjName:    fileName,
   523  		Reader:     readers.NewBytes(dataRemote),
   524  	}
   525  	_, err = api.PutObject(&putArgs)
   526  	tassert.CheckFatal(t, err)
   527  
   528  	resRemote, err := api.ListObjects(baseParams, bckRemote, msg, api.ListArgs{})
   529  	tassert.CheckFatal(t, err)
   530  
   531  	if len(resLocal.Entries) != 1 {
   532  		t.Fatalf("Expected number of files in ais bucket (%s) does not match: expected %v, got %v",
   533  			bckRemote.String(), 1, len(resLocal.Entries))
   534  	}
   535  
   536  	for _, en := range resRemote.Entries {
   537  		if en.Name == fileName {
   538  			found = true
   539  			break
   540  		}
   541  	}
   542  
   543  	if !found {
   544  		t.Fatalf("File (%s) not found in cloud bucket (%s)", fileName, bckRemote.String())
   545  	}
   546  
   547  	// Get
   548  	oahLocal, err := api.GetObject(baseParams, bckLocal, fileName, nil)
   549  	tassert.CheckFatal(t, err)
   550  	lenLocal := oahLocal.Size()
   551  	oahRemote, err := api.GetObject(baseParams, bckRemote, fileName, nil)
   552  	tassert.CheckFatal(t, err)
   553  	lenRemote := oahRemote.Size()
   554  
   555  	if lenLocal == lenRemote {
   556  		t.Errorf("Local file and cloud file have same size, expected: local (%v) cloud (%v) got: local (%v) cloud (%v)",
   557  			len(dataLocal), len(dataRemote), lenLocal, lenRemote)
   558  	}
   559  
   560  	// Delete
   561  	err = api.DeleteObject(baseParams, bckRemote, fileName)
   562  	tassert.CheckFatal(t, err)
   563  
   564  	oahLocal, err = api.GetObject(baseParams, bckLocal, fileName, nil)
   565  	tassert.CheckFatal(t, err)
   566  	lenLocal = oahLocal.Size()
   567  
   568  	// Check that local object still exists
   569  	if lenLocal != int64(len(dataLocal)) {
   570  		t.Errorf("Local file %s deleted", fileName)
   571  	}
   572  
   573  	// Check that cloud object is deleted
   574  	_, err = api.HeadObject(baseParams, bckRemote, fileName, apc.FltExistsOutside, false /*silent*/)
   575  	if !strings.Contains(err.Error(), strconv.Itoa(http.StatusNotFound)) {
   576  		t.Errorf("Remote file %s not deleted", fileName)
   577  	}
   578  
   579  	// Set Props Object
   580  	_, err = api.SetBucketProps(baseParams, bckLocal, bucketPropsLocal)
   581  	tassert.CheckFatal(t, err)
   582  
   583  	_, err = api.SetBucketProps(baseParams, bckRemote, bucketPropsRemote)
   584  	tassert.CheckFatal(t, err)
   585  
   586  	// Validate ais bucket props are set
   587  	localProps, err := api.HeadBucket(baseParams, bckLocal, true /* don't add */)
   588  	tassert.CheckFatal(t, err)
   589  	validateBucketProps(t, bucketPropsLocal, localProps)
   590  
   591  	// Validate cloud bucket props are set
   592  	cloudProps, err := api.HeadBucket(baseParams, bckRemote, true /* don't add */)
   593  	tassert.CheckFatal(t, err)
   594  	validateBucketProps(t, bucketPropsRemote, cloudProps)
   595  
   596  	// Reset ais bucket props and validate they are reset
   597  	_, err = api.ResetBucketProps(baseParams, bckLocal)
   598  	tassert.CheckFatal(t, err)
   599  	localProps, err = api.HeadBucket(baseParams, bckLocal, true /* don't add */)
   600  	tassert.CheckFatal(t, err)
   601  	validateBucketProps(t, &defLocalProps, localProps)
   602  
   603  	// Check if cloud bucket props remain the same
   604  	cloudProps, err = api.HeadBucket(baseParams, bckRemote, true /* don't add */)
   605  	tassert.CheckFatal(t, err)
   606  	validateBucketProps(t, bucketPropsRemote, cloudProps)
   607  
   608  	// Reset cloud bucket props
   609  	_, err = api.ResetBucketProps(baseParams, bckRemote)
   610  	tassert.CheckFatal(t, err)
   611  	cloudProps, err = api.HeadBucket(baseParams, bckRemote, true /* don't add */)
   612  	tassert.CheckFatal(t, err)
   613  	validateBucketProps(t, &defRemoteProps, cloudProps)
   614  
   615  	// Check if ais bucket props remain the same
   616  	localProps, err = api.HeadBucket(baseParams, bckLocal, true /* don't add */)
   617  	tassert.CheckFatal(t, err)
   618  	validateBucketProps(t, &defLocalProps, localProps)
   619  }
   620  
   621  func Test_coldgetmd5(t *testing.T) {
   622  	var (
   623  		m = ioContext{
   624  			t:        t,
   625  			bck:      cliBck,
   626  			num:      5,
   627  			fileSize: largeFileSize,
   628  			prefix:   "md5/obj-",
   629  		}
   630  		totalSize  = int64(uint64(m.num) * m.fileSize)
   631  		proxyURL   = tools.RandomProxyURL(t)
   632  		propsToSet *cmn.BpropsToSet
   633  	)
   634  
   635  	tools.CheckSkip(t, &tools.SkipTestArgs{RemoteBck: true, Bck: m.bck})
   636  
   637  	m.initAndSaveState(true /*cleanup*/)
   638  
   639  	baseParams := tools.BaseAPIParams(proxyURL)
   640  	p, err := api.HeadBucket(baseParams, m.bck, false /* don't add */)
   641  	tassert.CheckFatal(t, err)
   642  
   643  	t.Cleanup(func() {
   644  		propsToSet = &cmn.BpropsToSet{
   645  			Cksum: &cmn.CksumConfToSet{
   646  				ValidateColdGet: apc.Ptr(p.Cksum.ValidateColdGet),
   647  			},
   648  		}
   649  		_, err = api.SetBucketProps(baseParams, m.bck, propsToSet)
   650  		tassert.CheckError(t, err)
   651  		m.del()
   652  	})
   653  
   654  	m.remotePuts(true /*evict*/)
   655  
   656  	// Disable Cold Get Validation
   657  	if p.Cksum.ValidateColdGet {
   658  		propsToSet = &cmn.BpropsToSet{
   659  			Cksum: &cmn.CksumConfToSet{
   660  				ValidateColdGet: apc.Ptr(false),
   661  			},
   662  		}
   663  		_, err = api.SetBucketProps(baseParams, m.bck, propsToSet)
   664  		tassert.CheckFatal(t, err)
   665  	}
   666  
   667  	start := time.Now()
   668  	m.gets(nil /*api.GetArgs*/, false /*withValidation*/)
   669  	tlog.Logf("GET %s without MD5 validation: %v\n", cos.ToSizeIEC(totalSize, 0), time.Since(start))
   670  
   671  	m.evict()
   672  
   673  	// Enable cold get validation.
   674  	propsToSet = &cmn.BpropsToSet{
   675  		Cksum: &cmn.CksumConfToSet{
   676  			ValidateColdGet: apc.Ptr(true),
   677  		},
   678  	}
   679  	_, err = api.SetBucketProps(baseParams, m.bck, propsToSet)
   680  	tassert.CheckFatal(t, err)
   681  
   682  	start = time.Now()
   683  	m.gets(nil /*api.GetArgs*/, true /*withValidation*/)
   684  	tlog.Logf("GET %s with MD5 validation:    %v\n", cos.ToSizeIEC(totalSize, 0), time.Since(start))
   685  }
   686  
   687  func TestHeadBucket(t *testing.T) {
   688  	var (
   689  		proxyURL   = tools.RandomProxyURL(t)
   690  		baseParams = tools.BaseAPIParams(proxyURL)
   691  		bck        = cmn.Bck{
   692  			Name:     testBucketName,
   693  			Provider: apc.AIS,
   694  		}
   695  	)
   696  
   697  	tools.CreateBucket(t, proxyURL, bck, nil, true /*cleanup*/)
   698  
   699  	bckPropsToSet := &cmn.BpropsToSet{
   700  		Cksum: &cmn.CksumConfToSet{
   701  			ValidateWarmGet: apc.Ptr(true),
   702  		},
   703  		LRU: &cmn.LRUConfToSet{
   704  			Enabled: apc.Ptr(true),
   705  		},
   706  	}
   707  	_, err := api.SetBucketProps(baseParams, bck, bckPropsToSet)
   708  	tassert.CheckFatal(t, err)
   709  
   710  	p, err := api.HeadBucket(baseParams, bck, false /* don't add */)
   711  	tassert.CheckFatal(t, err)
   712  
   713  	validateBucketProps(t, bckPropsToSet, p)
   714  }
   715  
   716  func TestHeadRemoteBucket(t *testing.T) {
   717  	var (
   718  		proxyURL   = tools.RandomProxyURL(t)
   719  		baseParams = tools.BaseAPIParams(proxyURL)
   720  		bck        = cliBck
   721  	)
   722  
   723  	tools.CheckSkip(t, &tools.SkipTestArgs{RemoteBck: true, Bck: bck})
   724  
   725  	bckPropsToSet := &cmn.BpropsToSet{
   726  		Cksum: &cmn.CksumConfToSet{
   727  			ValidateWarmGet: apc.Ptr(true),
   728  			ValidateColdGet: apc.Ptr(true),
   729  		},
   730  		LRU: &cmn.LRUConfToSet{
   731  			Enabled: apc.Ptr(true),
   732  		},
   733  	}
   734  	_, err := api.SetBucketProps(baseParams, bck, bckPropsToSet)
   735  	tassert.CheckFatal(t, err)
   736  	defer resetBucketProps(t, proxyURL, bck)
   737  
   738  	p, err := api.HeadBucket(baseParams, bck, true /* don't add */)
   739  	tassert.CheckFatal(t, err)
   740  	validateBucketProps(t, bckPropsToSet, p)
   741  }
   742  
   743  func TestHeadNonexistentBucket(t *testing.T) {
   744  	var (
   745  		proxyURL   = tools.RandomProxyURL(t)
   746  		baseParams = tools.BaseAPIParams(proxyURL)
   747  	)
   748  
   749  	bucket, err := tools.GenerateNonexistentBucketName("head", baseParams)
   750  	tassert.CheckFatal(t, err)
   751  
   752  	bck := cmn.Bck{
   753  		Name:     bucket,
   754  		Provider: apc.AIS,
   755  	}
   756  
   757  	_, err = api.HeadBucket(baseParams, bck, true /* don't add */)
   758  	if err == nil {
   759  		t.Fatalf("Expected an error, but go no errors.")
   760  	}
   761  
   762  	status := api.HTTPStatus(err)
   763  	if status != http.StatusNotFound {
   764  		t.Errorf("Expected status %d, got %d", http.StatusNotFound, status)
   765  	}
   766  }
   767  
   768  // 1.	PUT file
   769  // 2.	Change contents of the file or change XXHash
   770  // 3.	GET file.
   771  // NOTE: The following test can only work when running on a local setup
   772  // (targets are co-located with where this test is running from, because
   773  // it searches a local oldFileIfo system).
   774  func TestChecksumValidateOnWarmGetForRemoteBucket(t *testing.T) {
   775  	var (
   776  		m = ioContext{
   777  			t:        t,
   778  			bck:      cliBck,
   779  			num:      3,
   780  			fileSize: cos.KiB,
   781  			prefix:   "cksum/obj-",
   782  		}
   783  
   784  		proxyURL   = tools.RandomProxyURL(t)
   785  		baseParams = tools.BaseAPIParams(proxyURL)
   786  	)
   787  
   788  	m.init(true /*cleanup*/)
   789  
   790  	tools.CheckSkip(t, &tools.SkipTestArgs{RemoteBck: true, Bck: m.bck})
   791  
   792  	if docker.IsRunning() {
   793  		t.Skipf("test %q requires xattrs to be set, doesn't work with docker", t.Name())
   794  	}
   795  
   796  	p, err := api.HeadBucket(baseParams, m.bck, false /* don't add */)
   797  	tassert.CheckFatal(t, err)
   798  
   799  	_ = mock.NewTarget(mock.NewBaseBownerMock(
   800  		meta.NewBck(
   801  			m.bck.Name, m.bck.Provider, cmn.NsGlobal,
   802  			&cmn.Bprops{Cksum: cmn.CksumConf{Type: cos.ChecksumXXHash}, Extra: p.Extra, BID: 0xa73b9f11},
   803  		),
   804  	))
   805  
   806  	initMountpaths(t, proxyURL)
   807  
   808  	m.puts()
   809  
   810  	t.Cleanup(func() {
   811  		propsToSet := &cmn.BpropsToSet{
   812  			Cksum: &cmn.CksumConfToSet{
   813  				Type:            apc.Ptr(p.Cksum.Type),
   814  				ValidateWarmGet: apc.Ptr(p.Cksum.ValidateWarmGet),
   815  			},
   816  		}
   817  		_, err = api.SetBucketProps(baseParams, m.bck, propsToSet)
   818  		tassert.CheckError(t, err)
   819  		m.del()
   820  	})
   821  
   822  	objName := m.objNames[0]
   823  	_, err = api.GetObjectWithValidation(baseParams, m.bck, objName, nil)
   824  	tassert.CheckError(t, err)
   825  
   826  	if !p.Cksum.ValidateWarmGet {
   827  		propsToSet := &cmn.BpropsToSet{
   828  			Cksum: &cmn.CksumConfToSet{
   829  				ValidateWarmGet: apc.Ptr(true),
   830  			},
   831  		}
   832  		_, err = api.SetBucketProps(baseParams, m.bck, propsToSet)
   833  		tassert.CheckFatal(t, err)
   834  	}
   835  
   836  	fqn := findObjOnDisk(m.bck, objName)
   837  	tools.CheckPathExists(t, fqn, false /*dir*/)
   838  	oldFileInfo, _ := os.Stat(fqn)
   839  
   840  	// Test when the contents of the file are changed
   841  	tlog.Logf("Changing contents of the file [%s]: %s\n", objName, fqn)
   842  	err = os.WriteFile(fqn, []byte("Contents of this file have been changed."), cos.PermRWR)
   843  	tassert.CheckFatal(t, err)
   844  	validateGETUponFileChangeForChecksumValidation(t, proxyURL, objName, fqn, oldFileInfo)
   845  
   846  	// Test when the xxHash of the file is changed
   847  	objName = m.objNames[1]
   848  	fqn = findObjOnDisk(m.bck, objName)
   849  	tools.CheckPathExists(t, fqn, false /*dir*/)
   850  	oldFileInfo, _ = os.Stat(fqn)
   851  
   852  	tlog.Logf("Changing file xattr[%s]: %s\n", objName, fqn)
   853  	err = tools.SetXattrCksum(fqn, m.bck, cos.NewCksum(cos.ChecksumXXHash, "01234"))
   854  	tassert.CheckError(t, err)
   855  	validateGETUponFileChangeForChecksumValidation(t, proxyURL, objName, fqn, oldFileInfo)
   856  
   857  	// Test for no checksum algo
   858  	objName = m.objNames[2]
   859  	fqn = findObjOnDisk(m.bck, objName)
   860  
   861  	if p.Cksum.Type != cos.ChecksumNone {
   862  		propsToSet := &cmn.BpropsToSet{
   863  			Cksum: &cmn.CksumConfToSet{
   864  				Type: apc.Ptr(cos.ChecksumNone),
   865  			},
   866  		}
   867  		_, err = api.SetBucketProps(baseParams, m.bck, propsToSet)
   868  		tassert.CheckFatal(t, err)
   869  	}
   870  
   871  	tlog.Logf("Changing file xattr[%s]: %s\n", objName, fqn)
   872  	err = tools.SetXattrCksum(fqn, m.bck, cos.NewCksum(cos.ChecksumXXHash, "01234abcde"))
   873  	tassert.CheckError(t, err)
   874  
   875  	_, err = api.GetObject(baseParams, m.bck, objName, nil)
   876  	tassert.Errorf(t, err == nil, "A GET on an object when checksum algo is none should pass. Error: %v", err)
   877  }
   878  
   879  func TestEvictRemoteBucket(t *testing.T) {
   880  	t.Run("Cloud/KeepMD", func(t *testing.T) { testEvictRemoteBucket(t, cliBck, true) })
   881  	t.Run("Cloud/DeleteMD", func(t *testing.T) { testEvictRemoteBucket(t, cliBck, false) })
   882  	t.Run("RemoteAIS", testEvictRemoteAISBucket)
   883  }
   884  
   885  func testEvictRemoteAISBucket(t *testing.T) {
   886  	tools.CheckSkip(t, &tools.SkipTestArgs{RequiresRemoteCluster: true})
   887  	bck := cmn.Bck{
   888  		Name:     trand.String(10),
   889  		Provider: apc.AIS,
   890  		Ns: cmn.Ns{
   891  			UUID: tools.RemoteCluster.UUID,
   892  		},
   893  	}
   894  	tools.CreateBucket(t, proxyURL, bck, nil, true /*cleanup*/)
   895  	testEvictRemoteBucket(t, bck, false)
   896  }
   897  
   898  func testEvictRemoteBucket(t *testing.T, bck cmn.Bck, keepMD bool) {
   899  	var (
   900  		m = ioContext{
   901  			t:        t,
   902  			bck:      bck,
   903  			num:      10,
   904  			fileSize: largeFileSize,
   905  			prefix:   "evict/obj-",
   906  		}
   907  		proxyURL   = tools.RandomProxyURL(t)
   908  		baseParams = tools.BaseAPIParams(proxyURL)
   909  	)
   910  
   911  	tools.CheckSkip(t, &tools.SkipTestArgs{RemoteBck: true, Bck: m.bck})
   912  	m.initAndSaveState(true /*cleanup*/)
   913  
   914  	t.Cleanup(func() {
   915  		m.del()
   916  		resetBucketProps(t, proxyURL, m.bck)
   917  	})
   918  
   919  	m.remotePuts(false /*evict*/)
   920  
   921  	// Test property, mirror is disabled for cloud bucket that hasn't been accessed,
   922  	// even if system config says otherwise
   923  	_, err := api.SetBucketProps(baseParams, m.bck, &cmn.BpropsToSet{
   924  		Mirror: &cmn.MirrorConfToSet{Enabled: apc.Ptr(true)},
   925  	})
   926  	tassert.CheckFatal(t, err)
   927  
   928  	bProps, err := api.HeadBucket(baseParams, m.bck, true /* don't add */)
   929  	tassert.CheckFatal(t, err)
   930  	tassert.Fatalf(t, bProps.Mirror.Enabled, "test property hasn't changed")
   931  
   932  	// Wait for async mirroring to finish
   933  	flt := xact.ArgsMsg{Kind: apc.ActMakeNCopies, Bck: m.bck}
   934  	api.WaitForXactionIdle(baseParams, &flt)
   935  	time.Sleep(time.Second)
   936  
   937  	err = api.EvictRemoteBucket(baseParams, m.bck, keepMD)
   938  	tassert.CheckFatal(t, err)
   939  
   940  	for _, objName := range m.objNames {
   941  		exists := tools.CheckObjIsPresent(proxyURL, m.bck, objName)
   942  		tassert.Errorf(t, !exists, "object remains cached: %s", objName)
   943  	}
   944  	bProps, err = api.HeadBucket(baseParams, m.bck, true /* don't add */)
   945  	tassert.CheckFatal(t, err)
   946  	if keepMD {
   947  		tassert.Fatalf(t, bProps.Mirror.Enabled, "test property was reset")
   948  	} else {
   949  		tassert.Fatalf(t, !bProps.Mirror.Enabled, "test property not reset")
   950  	}
   951  }
   952  
   953  func validateGETUponFileChangeForChecksumValidation(t *testing.T, proxyURL, objName, fqn string,
   954  	oldFileInfo os.FileInfo) {
   955  	// Do a GET to see to check if a cold get was executed by comparing old and new size
   956  	var (
   957  		baseParams = tools.BaseAPIParams(proxyURL)
   958  		bck        = cliBck
   959  	)
   960  
   961  	_, err := api.GetObjectWithValidation(baseParams, bck, objName, nil)
   962  	tassert.CheckError(t, err)
   963  
   964  	tools.CheckPathExists(t, fqn, false /*dir*/)
   965  	newFileInfo, _ := os.Stat(fqn)
   966  	if newFileInfo.Size() != oldFileInfo.Size() {
   967  		t.Errorf("Expected size: %d, Actual Size: %d", oldFileInfo.Size(), newFileInfo.Size())
   968  	}
   969  }
   970  
   971  //  1. PUT file
   972  //  2. Change contents of the file or change XXHash
   973  //  3. GET file (first GET should fail with Internal Server Error and the
   974  //     second should fail with not found).
   975  //
   976  // Note: The following test can only work when running on a local setup
   977  // (targets are co-located with where this test is running from, because
   978  // it searches a local file system)
   979  func TestChecksumValidateOnWarmGetForBucket(t *testing.T) {
   980  	var (
   981  		m = ioContext{
   982  			t: t,
   983  			bck: cmn.Bck{
   984  				Provider: apc.AIS,
   985  				Name:     trand.String(15),
   986  				Props:    &cmn.Bprops{BID: 2},
   987  			},
   988  			num:      3,
   989  			fileSize: cos.KiB,
   990  		}
   991  
   992  		proxyURL   = tools.RandomProxyURL(t)
   993  		baseParams = tools.BaseAPIParams(proxyURL)
   994  		_          = mock.NewTarget(mock.NewBaseBownerMock(
   995  			meta.NewBck(
   996  				m.bck.Name, apc.AIS, cmn.NsGlobal,
   997  				&cmn.Bprops{Cksum: cmn.CksumConf{Type: cos.ChecksumXXHash}, BID: 1},
   998  			),
   999  			meta.CloneBck(&m.bck),
  1000  		))
  1001  		cksumConf = m.bck.DefaultProps(initialClusterConfig).Cksum
  1002  	)
  1003  
  1004  	m.init(true /*cleanup*/)
  1005  
  1006  	if docker.IsRunning() {
  1007  		t.Skipf("test %q requires write access to xattrs, doesn't work with docker", t.Name())
  1008  	}
  1009  
  1010  	initMountpaths(t, proxyURL)
  1011  
  1012  	tools.CreateBucket(t, proxyURL, m.bck, nil, true /*cleanup*/)
  1013  
  1014  	m.puts()
  1015  
  1016  	if !cksumConf.ValidateWarmGet {
  1017  		propsToSet := &cmn.BpropsToSet{
  1018  			Cksum: &cmn.CksumConfToSet{
  1019  				ValidateWarmGet: apc.Ptr(true),
  1020  			},
  1021  		}
  1022  		_, err := api.SetBucketProps(baseParams, m.bck, propsToSet)
  1023  		tassert.CheckFatal(t, err)
  1024  	}
  1025  
  1026  	// Test changing the file content.
  1027  	objName := m.objNames[0]
  1028  	fqn := findObjOnDisk(m.bck, objName)
  1029  	tlog.Logf("Changing contents of the file [%s]: %s\n", objName, fqn)
  1030  	err := os.WriteFile(fqn, []byte("Contents of this file have been changed."), cos.PermRWR)
  1031  	tassert.CheckFatal(t, err)
  1032  	executeTwoGETsForChecksumValidation(proxyURL, m.bck, objName, t)
  1033  
  1034  	// Test changing the file xattr.
  1035  	objName = m.objNames[1]
  1036  	fqn = findObjOnDisk(m.bck, objName)
  1037  	tlog.Logf("Changing file xattr[%s]: %s\n", objName, fqn)
  1038  	err = tools.SetXattrCksum(fqn, m.bck, cos.NewCksum(cos.ChecksumXXHash, "01234abcde"))
  1039  	tassert.CheckError(t, err)
  1040  	executeTwoGETsForChecksumValidation(proxyURL, m.bck, objName, t)
  1041  
  1042  	// Test for none checksum algorithm.
  1043  	objName = m.objNames[2]
  1044  	fqn = findObjOnDisk(m.bck, objName)
  1045  
  1046  	if cksumConf.Type != cos.ChecksumNone {
  1047  		propsToSet := &cmn.BpropsToSet{
  1048  			Cksum: &cmn.CksumConfToSet{
  1049  				Type: apc.Ptr(cos.ChecksumNone),
  1050  			},
  1051  		}
  1052  		_, err = api.SetBucketProps(baseParams, m.bck, propsToSet)
  1053  		tassert.CheckFatal(t, err)
  1054  	}
  1055  
  1056  	tlog.Logf("Changing file xattr[%s]: %s\n", objName, fqn)
  1057  	err = tools.SetXattrCksum(fqn, m.bck, cos.NewCksum(cos.ChecksumXXHash, "01234abcde"))
  1058  	tassert.CheckError(t, err)
  1059  	_, err = api.GetObject(baseParams, m.bck, objName, nil)
  1060  	tassert.CheckError(t, err)
  1061  }
  1062  
  1063  func executeTwoGETsForChecksumValidation(proxyURL string, bck cmn.Bck, objName string, t *testing.T) {
  1064  	baseParams := tools.BaseAPIParams(proxyURL)
  1065  	_, err := api.GetObjectWithValidation(baseParams, bck, objName, nil)
  1066  	if err == nil {
  1067  		t.Error("Error is nil, expected internal server error on a GET for an object")
  1068  	} else if !strings.Contains(err.Error(), "ErrBadCksum") {
  1069  		t.Errorf("Expected bad checksum error on a GET for a corrupted object, got [%v]", err)
  1070  	}
  1071  	// Execute another GET to make sure that the object is deleted
  1072  	_, err = api.GetObjectWithValidation(baseParams, bck, objName, nil)
  1073  	if err == nil {
  1074  		t.Error("Error is nil, expected not found on a second GET for a corrupted object")
  1075  	} else if !strings.Contains(err.Error(), "ErrNotFound") {
  1076  		t.Errorf("Expected Not Found on a second GET for a corrupted object, got [%v]", err)
  1077  	}
  1078  }
  1079  
  1080  // TODO: validate range checksums
  1081  func TestRangeRead(t *testing.T) {
  1082  	initMountpaths(t, tools.RandomProxyURL(t)) // to run findObjOnDisk() and validate range
  1083  
  1084  	runProviderTests(t, func(t *testing.T, bck *meta.Bck) {
  1085  		var (
  1086  			m = ioContext{
  1087  				t:         t,
  1088  				bck:       bck.Clone(),
  1089  				num:       5,
  1090  				fileSize:  5271,
  1091  				fixedSize: true,
  1092  			}
  1093  			proxyURL   = tools.RandomProxyURL(t)
  1094  			baseParams = tools.BaseAPIParams(proxyURL)
  1095  			cksumProps = bck.CksumConf()
  1096  		)
  1097  
  1098  		m.init(true /*cleanup*/)
  1099  		m.puts()
  1100  		if m.bck.IsRemote() {
  1101  			defer m.del()
  1102  		}
  1103  		objName := m.objNames[0]
  1104  
  1105  		defer func() {
  1106  			tlog.Logln("Cleaning up...")
  1107  			propsToSet := &cmn.BpropsToSet{
  1108  				Cksum: &cmn.CksumConfToSet{
  1109  					EnableReadRange: apc.Ptr(cksumProps.EnableReadRange),
  1110  				},
  1111  			}
  1112  			_, err := api.SetBucketProps(baseParams, m.bck, propsToSet)
  1113  			tassert.CheckError(t, err)
  1114  		}()
  1115  
  1116  		tlog.Logln("Valid range with object checksum...")
  1117  		// Validate that entire object checksum is being returned
  1118  		if cksumProps.EnableReadRange {
  1119  			propsToSet := &cmn.BpropsToSet{
  1120  				Cksum: &cmn.CksumConfToSet{
  1121  					EnableReadRange: apc.Ptr(false),
  1122  				},
  1123  			}
  1124  			_, err := api.SetBucketProps(baseParams, m.bck, propsToSet)
  1125  			tassert.CheckFatal(t, err)
  1126  		}
  1127  		testValidCases(t, proxyURL, bck.Clone(), cksumProps.Type, m.fileSize, objName, true)
  1128  
  1129  		// Validate only that range checksum is being returned
  1130  		tlog.Logln("Valid range with range checksum...")
  1131  		if !cksumProps.EnableReadRange {
  1132  			propsToSet := &cmn.BpropsToSet{
  1133  				Cksum: &cmn.CksumConfToSet{
  1134  					EnableReadRange: apc.Ptr(true),
  1135  				},
  1136  			}
  1137  			_, err := api.SetBucketProps(baseParams, bck.Clone(), propsToSet)
  1138  			tassert.CheckFatal(t, err)
  1139  		}
  1140  		testValidCases(t, proxyURL, m.bck, cksumProps.Type, m.fileSize, objName, false)
  1141  
  1142  		tlog.Logln("Valid range query...")
  1143  		verifyValidRangesQuery(t, proxyURL, m.bck, objName, "bytes=-1", 1)
  1144  		verifyValidRangesQuery(t, proxyURL, m.bck, objName, "bytes=0-", int64(m.fileSize))
  1145  		verifyValidRangesQuery(t, proxyURL, m.bck, objName, "bytes=10-", int64(m.fileSize-10))
  1146  		verifyValidRangesQuery(t, proxyURL, m.bck, objName, fmt.Sprintf("bytes=-%d", m.fileSize), int64(m.fileSize))
  1147  		verifyValidRangesQuery(t, proxyURL, m.bck, objName, fmt.Sprintf("bytes=-%d", m.fileSize+2), int64(m.fileSize))
  1148  
  1149  		tlog.Logln("======================================================================")
  1150  		tlog.Logln("Invalid range query:")
  1151  		verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "potatoes=0-1")
  1152  		verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes")
  1153  		verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, fmt.Sprintf("bytes=%d-", m.fileSize+1))
  1154  		verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, fmt.Sprintf("bytes=%d-%d", m.fileSize+1, m.fileSize+2))
  1155  		verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=0--1")
  1156  		verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=1-0")
  1157  		verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=-1-0")
  1158  		verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=-1-2")
  1159  		verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=10--1")
  1160  		verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=1-2,4-6")
  1161  		verifyInvalidRangesQuery(t, proxyURL, m.bck, objName, "bytes=--1")
  1162  	})
  1163  }
  1164  
  1165  // TODO: validate range checksum if enabled
  1166  func testValidCases(t *testing.T, proxyURL string, bck cmn.Bck, cksumType string, fileSize uint64, objName string,
  1167  	checkEntireObjCksum bool) {
  1168  	// Range-Read the entire file in 500 increments
  1169  	byteRange := int64(500)
  1170  	iterations := int64(fileSize) / byteRange
  1171  	for i := int64(0); i < iterations; i += byteRange {
  1172  		verifyValidRanges(t, proxyURL, bck, cksumType, objName, i, byteRange, byteRange, checkEntireObjCksum)
  1173  	}
  1174  
  1175  	verifyValidRanges(t, proxyURL, bck, cksumType, objName, byteRange*iterations, byteRange,
  1176  		int64(fileSize)%byteRange, checkEntireObjCksum)
  1177  }
  1178  
  1179  func verifyValidRanges(t *testing.T, proxyURL string, bck cmn.Bck, cksumType, objName string,
  1180  	offset, length, expectedLength int64, checkEntireObjCksum bool) {
  1181  	var (
  1182  		w          = bytes.NewBuffer(nil)
  1183  		rng        = cmn.MakeRangeHdr(offset, length)
  1184  		hdr        = http.Header{cos.HdrRange: []string{rng}}
  1185  		baseParams = tools.BaseAPIParams(proxyURL)
  1186  		fqn        = findObjOnDisk(bck, objName)
  1187  		args       = api.GetArgs{Writer: w, Header: hdr}
  1188  	)
  1189  
  1190  	oah, err := api.GetObjectWithValidation(baseParams, bck, objName, &args)
  1191  	if err != nil {
  1192  		if !checkEntireObjCksum {
  1193  			t.Errorf("Failed to GET %s: %v", bck.Cname(objName), err)
  1194  		} else {
  1195  			if ckErr, ok := err.(*cmn.ErrInvalidCksum); ok {
  1196  				file, err := os.Open(fqn)
  1197  				if err != nil {
  1198  					t.Fatalf("Unable to open file: %s. Error:  %v", fqn, err)
  1199  				}
  1200  				defer file.Close()
  1201  				_, cksum, err := cos.CopyAndChecksum(io.Discard, file, nil, cksumType)
  1202  				if err != nil {
  1203  					t.Errorf("Unable to compute cksum of file: %s. Error:  %s", fqn, err)
  1204  				}
  1205  				if cksum.Value() != ckErr.Expected() {
  1206  					t.Errorf("Expected entire object checksum [%s], checksum returned in response [%s]",
  1207  						ckErr.Expected(), cksum)
  1208  				}
  1209  			} else {
  1210  				t.Errorf("Unexpected error returned [%v].", err)
  1211  			}
  1212  		}
  1213  	} else if oah.Size() != expectedLength {
  1214  		t.Errorf("number of bytes received (%d) is different from expected (%d)", oah.Size(), expectedLength)
  1215  	}
  1216  
  1217  	file, err := os.Open(fqn)
  1218  	if err != nil {
  1219  		t.Fatalf("Unable to open file: %s. Error:  %v", fqn, err)
  1220  	}
  1221  	defer file.Close()
  1222  	outputBytes := w.Bytes()
  1223  	sectionReader := io.NewSectionReader(file, offset, length)
  1224  	expectedBytesBuffer := bytes.NewBuffer(nil)
  1225  	_, err = expectedBytesBuffer.ReadFrom(sectionReader)
  1226  	if err != nil {
  1227  		t.Errorf("Unable to read the file %s, from offset: %d and length: %d. Error: %v", fqn, offset, length, err)
  1228  	}
  1229  	expectedBytes := expectedBytesBuffer.Bytes()
  1230  	if len(outputBytes) != len(expectedBytes) {
  1231  		t.Errorf("Bytes length mismatch. Expected bytes: [%d]. Actual bytes: [%d]", len(expectedBytes), len(outputBytes))
  1232  	}
  1233  	if int64(len(outputBytes)) != expectedLength {
  1234  		t.Errorf("Returned bytes don't match expected length. Expected length: [%d]. Output length: [%d]",
  1235  			expectedLength, len(outputBytes))
  1236  	}
  1237  	for i := range len(expectedBytes) {
  1238  		if expectedBytes[i] != outputBytes[i] {
  1239  			t.Errorf("Byte mismatch. Expected: %v, Actual: %v", string(expectedBytes), string(outputBytes))
  1240  		}
  1241  	}
  1242  }
  1243  
  1244  func verifyValidRangesQuery(t *testing.T, proxyURL string, bck cmn.Bck, objName, rangeQuery string, expectedLength int64) {
  1245  	var (
  1246  		baseParams = tools.BaseAPIParams(proxyURL)
  1247  		hdr        = http.Header{cos.HdrRange: {rangeQuery}}
  1248  		args       = api.GetArgs{Header: hdr}
  1249  	)
  1250  	oah, err := api.GetObject(baseParams, bck, objName, &args)
  1251  	tassert.CheckFatal(t, err)
  1252  
  1253  	tlog.Logf("rangeQuery=%s, n=%d\n", rangeQuery, oah.Size()) // DEBUG
  1254  
  1255  	// check size
  1256  	tassert.Errorf(t, oah.Size() == expectedLength, "expected range length %d, got %d",
  1257  		expectedLength, oah.Size())
  1258  
  1259  	// check read range response headers
  1260  	respHeader := oah.RespHeader()
  1261  	acceptRanges := respHeader.Get(cos.HdrAcceptRanges)
  1262  	tassert.Errorf(t, acceptRanges == "bytes", "%q header is not set correctly: %s",
  1263  		cos.HdrAcceptRanges, acceptRanges)
  1264  	contentRange := respHeader.Get(cos.HdrContentRange)
  1265  	tassert.Errorf(t, contentRange != "", "%q header should be set", cos.HdrContentRange)
  1266  }
  1267  
  1268  func verifyInvalidRangesQuery(t *testing.T, proxyURL string, bck cmn.Bck, objName, rangeQuery string) {
  1269  	var (
  1270  		baseParams = tools.BaseAPIParams(proxyURL)
  1271  		hdr        = http.Header{cos.HdrRange: {rangeQuery}}
  1272  		args       = api.GetArgs{Header: hdr}
  1273  	)
  1274  	_, err := api.GetObjectWithValidation(baseParams, bck, objName, &args)
  1275  	tassert.Errorf(t, err != nil, "must fail for %q combination", rangeQuery)
  1276  }
  1277  
  1278  func Test_checksum(t *testing.T) {
  1279  	var (
  1280  		m = ioContext{
  1281  			t:        t,
  1282  			bck:      cliBck,
  1283  			num:      5,
  1284  			fileSize: largeFileSize,
  1285  		}
  1286  		totalSize  = int64(uint64(m.num) * m.fileSize)
  1287  		proxyURL   = tools.RandomProxyURL(t)
  1288  		baseParams = tools.BaseAPIParams(proxyURL)
  1289  	)
  1290  
  1291  	tools.CheckSkip(t, &tools.SkipTestArgs{Long: true, RemoteBck: true, Bck: m.bck})
  1292  
  1293  	p, err := api.HeadBucket(baseParams, m.bck, false /* don't add */)
  1294  	tassert.CheckFatal(t, err)
  1295  
  1296  	t.Cleanup(func() {
  1297  		propsToSet := &cmn.BpropsToSet{
  1298  			Cksum: &cmn.CksumConfToSet{
  1299  				Type:            apc.Ptr(p.Cksum.Type),
  1300  				ValidateColdGet: apc.Ptr(p.Cksum.ValidateColdGet),
  1301  			},
  1302  		}
  1303  		_, err = api.SetBucketProps(baseParams, m.bck, propsToSet)
  1304  		tassert.CheckError(t, err)
  1305  		m.del()
  1306  	})
  1307  
  1308  	m.remotePuts(true /*evict*/)
  1309  
  1310  	// Disable checkum.
  1311  	if p.Cksum.Type != cos.ChecksumNone {
  1312  		propsToSet := &cmn.BpropsToSet{
  1313  			Cksum: &cmn.CksumConfToSet{
  1314  				Type: apc.Ptr(cos.ChecksumNone),
  1315  			},
  1316  		}
  1317  		_, err = api.SetBucketProps(baseParams, m.bck, propsToSet)
  1318  		tassert.CheckFatal(t, err)
  1319  	}
  1320  
  1321  	// Disable cold get validation.
  1322  	if p.Cksum.ValidateColdGet {
  1323  		propsToSet := &cmn.BpropsToSet{
  1324  			Cksum: &cmn.CksumConfToSet{
  1325  				ValidateColdGet: apc.Ptr(false),
  1326  			},
  1327  		}
  1328  		_, err = api.SetBucketProps(baseParams, m.bck, propsToSet)
  1329  		tassert.CheckFatal(t, err)
  1330  	}
  1331  
  1332  	start := time.Now()
  1333  	m.gets(nil /*api.GetArgs*/, false /*withValidate*/)
  1334  	tlog.Logf("GET %s without any checksum validation: %v\n", cos.ToSizeIEC(totalSize, 0), time.Since(start))
  1335  
  1336  	m.evict()
  1337  
  1338  	propsToSet := &cmn.BpropsToSet{
  1339  		Cksum: &cmn.CksumConfToSet{
  1340  			Type:            apc.Ptr(cos.ChecksumXXHash),
  1341  			ValidateColdGet: apc.Ptr(true),
  1342  		},
  1343  	}
  1344  	_, err = api.SetBucketProps(baseParams, m.bck, propsToSet)
  1345  	tassert.CheckFatal(t, err)
  1346  
  1347  	start = time.Now()
  1348  	m.gets(nil /*api.GetArgs*/, true /*withValidate*/)
  1349  	tlog.Logf("GET %s and validate checksum: %v\n", cos.ToSizeIEC(totalSize, 0), time.Since(start))
  1350  }
  1351  
  1352  func validateBucketProps(t *testing.T, expected *cmn.BpropsToSet, actual *cmn.Bprops) {
  1353  	// Apply changes on props that we have received. If after applying anything
  1354  	// has changed it means that the props were not applied.
  1355  	tmpProps := actual.Clone()
  1356  	tmpProps.Apply(expected)
  1357  	if !reflect.DeepEqual(tmpProps, actual) {
  1358  		t.Errorf("bucket props are not equal, expected: %+v, got: %+v", tmpProps, actual)
  1359  	}
  1360  }
  1361  
  1362  func resetBucketProps(t *testing.T, proxyURL string, bck cmn.Bck) {
  1363  	baseParams := tools.BaseAPIParams(proxyURL)
  1364  	_, err := api.ResetBucketProps(baseParams, bck)
  1365  	tassert.CheckError(t, err)
  1366  }
  1367  
  1368  func corruptSingleBitInFile(t *testing.T, bck cmn.Bck, objName string) {
  1369  	var (
  1370  		fqn     = findObjOnDisk(bck, objName)
  1371  		fi, err = os.Stat(fqn)
  1372  		b       = []byte{0}
  1373  	)
  1374  	tassert.CheckFatal(t, err)
  1375  	off := rand.Int63n(fi.Size())
  1376  	file, err := os.OpenFile(fqn, os.O_RDWR, cos.PermRWR)
  1377  	tassert.CheckFatal(t, err)
  1378  	_, err = file.Seek(off, 0)
  1379  	tassert.CheckFatal(t, err)
  1380  	_, err = file.Read(b)
  1381  	tassert.CheckFatal(t, err)
  1382  	bit := rand.Intn(8)
  1383  	b[0] ^= 1 << bit
  1384  	_, err = file.Seek(off, 0)
  1385  	tassert.CheckFatal(t, err)
  1386  	_, err = file.Write(b)
  1387  	tassert.CheckFatal(t, err)
  1388  	file.Close()
  1389  }
  1390  
  1391  func TestPutObjectWithChecksum(t *testing.T) {
  1392  	var (
  1393  		proxyURL     = tools.RandomProxyURL(t)
  1394  		baseParams   = tools.BaseAPIParams(proxyURL)
  1395  		bck          = cliBck
  1396  		basefileName = "mytestobj.txt"
  1397  		objData      = []byte("I am object data")
  1398  		badCksumVal  = "badchecksum"
  1399  	)
  1400  
  1401  	if bck.IsAIS() {
  1402  		tools.CreateBucket(t, proxyURL, bck, nil, true /*cleanup*/)
  1403  	} else {
  1404  		t.Cleanup(func() {
  1405  			m := ioContext{
  1406  				t:   t,
  1407  				bck: bck,
  1408  			}
  1409  			m.del(-1 /*delete all*/, 0 /* lsmsg.Flags */)
  1410  		})
  1411  	}
  1412  
  1413  	bprops, err := api.HeadBucket(baseParams, bck, false /* don't add */)
  1414  	tassert.CheckFatal(t, err)
  1415  
  1416  	// Enable Cold Get Validation
  1417  	if !bprops.Cksum.ValidateColdGet {
  1418  		propsToSet := &cmn.BpropsToSet{
  1419  			Cksum: &cmn.CksumConfToSet{
  1420  				ValidateColdGet: apc.Ptr(true),
  1421  			},
  1422  		}
  1423  		_, err = api.SetBucketProps(baseParams, bck, propsToSet)
  1424  		tassert.CheckFatal(t, err)
  1425  
  1426  		t.Cleanup(func() {
  1427  			propsToSet.Cksum.ValidateColdGet = apc.Ptr(false)
  1428  			_, err = api.SetBucketProps(baseParams, bck, propsToSet)
  1429  			tassert.CheckError(t, err)
  1430  		})
  1431  	}
  1432  
  1433  	putArgs := api.PutArgs{
  1434  		BaseParams: baseParams,
  1435  		Bck:        bck,
  1436  		Reader:     readers.NewBytes(objData),
  1437  	}
  1438  	for _, cksumType := range cos.SupportedChecksums() {
  1439  		if cksumType == cos.ChecksumNone {
  1440  			continue
  1441  		}
  1442  		fileName := basefileName + cksumType
  1443  		hasher := cos.NewCksumHash(cksumType)
  1444  		hasher.H.Write(objData)
  1445  		cksumValue := hex.EncodeToString(hasher.H.Sum(nil))
  1446  		putArgs.Cksum = cos.NewCksum(cksumType, badCksumVal)
  1447  		putArgs.ObjName = fileName
  1448  
  1449  		_, err := api.PutObject(&putArgs)
  1450  		if err == nil {
  1451  			t.Errorf("Bad checksum provided by the user, Expected an error")
  1452  		}
  1453  
  1454  		_, err = api.HeadObject(baseParams, bck, fileName, apc.FltExists, false /*silent*/)
  1455  		if err == nil || !strings.Contains(err.Error(), strconv.Itoa(http.StatusNotFound)) {
  1456  			t.Errorf("Object %s exists despite bad checksum", fileName)
  1457  		}
  1458  		putArgs.Cksum = cos.NewCksum(cksumType, cksumValue)
  1459  		oah, err := api.PutObject(&putArgs)
  1460  		if err != nil {
  1461  			t.Errorf("Correct checksum provided, Err encountered %v", err)
  1462  		}
  1463  		op, err := api.HeadObject(baseParams, bck, fileName, apc.FltPresent, false /*silent*/)
  1464  		if err != nil {
  1465  			t.Errorf("Object %s does not exist despite correct checksum", fileName)
  1466  		}
  1467  		attrs1 := oah.Attrs()
  1468  		attrs2 := op.ObjAttrs
  1469  		tassert.Errorf(t, attrs1.Equal(&attrs2), "PUT(obj) attrs %s != %s HEAD\n", attrs1.String(), attrs2.String())
  1470  	}
  1471  }
  1472  
  1473  func TestOperationsWithRanges(t *testing.T) {
  1474  	const (
  1475  		objCnt  = 50 // NOTE: must by a multiple of 10
  1476  		objSize = cos.KiB
  1477  	)
  1478  	proxyURL := tools.RandomProxyURL(t)
  1479  
  1480  	runProviderTests(t, func(t *testing.T, bck *meta.Bck) {
  1481  		for _, evict := range []bool{false, true} {
  1482  			t.Run(fmt.Sprintf("evict=%t", evict), func(t *testing.T) {
  1483  				if evict && bck.IsAIS() {
  1484  					t.Skip("operation `evict` is not applicable to AIS buckets")
  1485  				}
  1486  
  1487  				var (
  1488  					objList   = make([]string, 0, objCnt)
  1489  					cksumType = bck.Props.Cksum.Type
  1490  				)
  1491  
  1492  				for i := range objCnt / 2 {
  1493  					objList = append(objList,
  1494  						fmt.Sprintf("test/a-%04d", i),
  1495  						fmt.Sprintf("test/b-%04d", i),
  1496  					)
  1497  				}
  1498  				for _, objName := range objList {
  1499  					r, _ := readers.NewRand(objSize, cksumType)
  1500  					_, err := api.PutObject(&api.PutArgs{
  1501  						BaseParams: baseParams,
  1502  						Bck:        bck.Clone(),
  1503  						ObjName:    objName,
  1504  						Reader:     r,
  1505  						Size:       objSize,
  1506  					})
  1507  					tassert.CheckFatal(t, err)
  1508  				}
  1509  
  1510  				tests := []struct {
  1511  					// Title to print out while testing.
  1512  					name string
  1513  					// A range of objects.
  1514  					rangeStr string
  1515  					// Total number of objects expected to be deleted/evicted.
  1516  					delta int
  1517  				}{
  1518  					{
  1519  						"Trying to delete/evict objects with invalid prefix",
  1520  						"file/a-{0..10}",
  1521  						0,
  1522  					},
  1523  					{
  1524  						"Trying to delete/evict objects out of range",
  1525  						"test/a-" + fmt.Sprintf("{%d..%d}", objCnt+10, objCnt+110),
  1526  						0,
  1527  					},
  1528  					{
  1529  						fmt.Sprintf("Deleting/Evicting %d objects with prefix 'a-'", objCnt/10),
  1530  						"test/a-" + fmt.Sprintf("{%04d..%04d}", (objCnt-objCnt/5)/2, objCnt/2),
  1531  						objCnt / 10,
  1532  					},
  1533  					{
  1534  						fmt.Sprintf("Deleting/Evicting %d objects (short range)", objCnt/5),
  1535  						"test/b-" + fmt.Sprintf("{%04d..%04d}", 1, objCnt/5),
  1536  						objCnt / 5,
  1537  					},
  1538  					{
  1539  						"Deleting/Evicting objects with empty range",
  1540  						"test/b-",
  1541  						objCnt/2 - objCnt/5,
  1542  					},
  1543  				}
  1544  
  1545  				var (
  1546  					totalFiles  = objCnt
  1547  					baseParams  = tools.BaseAPIParams(proxyURL)
  1548  					waitTimeout = 10 * time.Second
  1549  					b           = bck.Clone()
  1550  				)
  1551  
  1552  				if bck.IsRemote() {
  1553  					waitTimeout = 40 * time.Second
  1554  				}
  1555  
  1556  				for idx, test := range tests {
  1557  					tlog.Logf("%d. %s; range: [%s]\n", idx+1, test.name, test.rangeStr)
  1558  
  1559  					var (
  1560  						err  error
  1561  						xid  string
  1562  						kind string
  1563  						msg  = &apc.LsoMsg{Prefix: "test/"}
  1564  					)
  1565  					if evict {
  1566  						xid, err = api.EvictMultiObj(baseParams, b, nil /*lst objnames*/, test.rangeStr)
  1567  						msg.Flags = apc.LsObjCached
  1568  						kind = apc.ActEvictObjects
  1569  					} else {
  1570  						xid, err = api.DeleteMultiObj(baseParams, b, nil /*lst objnames*/, test.rangeStr)
  1571  						kind = apc.ActDeleteObjects
  1572  					}
  1573  					if err != nil {
  1574  						t.Error(err)
  1575  						continue
  1576  					}
  1577  
  1578  					args := xact.ArgsMsg{ID: xid, Kind: kind, Timeout: waitTimeout}
  1579  					_, err = api.WaitForXactionIC(baseParams, &args)
  1580  					tassert.CheckFatal(t, err)
  1581  
  1582  					totalFiles -= test.delta
  1583  					objList, err := api.ListObjects(baseParams, b, msg, api.ListArgs{})
  1584  					if err != nil {
  1585  						t.Error(err)
  1586  						continue
  1587  					}
  1588  					if len(objList.Entries) != totalFiles {
  1589  						t.Errorf("Incorrect number of remaining objects: %d, should be %d",
  1590  							len(objList.Entries), totalFiles)
  1591  						continue
  1592  					}
  1593  
  1594  					tlog.Logf("  %d objects have been deleted/evicted\n", test.delta)
  1595  				}
  1596  
  1597  				msg := &apc.LsoMsg{Prefix: "test/"}
  1598  				lst, err := api.ListObjects(baseParams, b, msg, api.ListArgs{})
  1599  				tassert.CheckFatal(t, err)
  1600  
  1601  				tlog.Logf("Cleaning up remaining objects...\n")
  1602  				for _, obj := range lst.Entries {
  1603  					err := tools.Del(proxyURL, b, obj.Name, nil, nil, true /*silent*/)
  1604  					tassert.CheckError(t, err)
  1605  				}
  1606  			})
  1607  		}
  1608  	})
  1609  }