github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/tools/common.go (about) 1 // Package tools provides common tools and utilities for all unit and integration tests 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package tools 6 7 import ( 8 "errors" 9 "fmt" 10 "net/http" 11 "os" 12 "path/filepath" 13 "strconv" 14 "strings" 15 "testing" 16 17 "github.com/NVIDIA/aistore/api" 18 "github.com/NVIDIA/aistore/api/apc" 19 "github.com/NVIDIA/aistore/cmn" 20 "github.com/NVIDIA/aistore/cmn/cos" 21 "github.com/NVIDIA/aistore/cmn/fname" 22 "github.com/NVIDIA/aistore/core/meta" 23 "github.com/NVIDIA/aistore/tools/readers" 24 "github.com/NVIDIA/aistore/tools/tassert" 25 "github.com/NVIDIA/aistore/tools/trand" 26 ) 27 28 // Generates an object name that hashes to a different target than `baseName`. 29 func GenerateNotConflictingObjectName(baseName, newNamePrefix string, bck cmn.Bck, smap *meta.Smap) string { 30 // Init digests - HrwTarget() requires it 31 smap.InitDigests() 32 33 newName := newNamePrefix 34 35 cbck := meta.CloneBck(&bck) 36 baseNameHrw, e1 := smap.HrwName2T(cbck.MakeUname(baseName)) 37 newNameHrw, e2 := smap.HrwName2T(cbck.MakeUname(newName)) 38 cos.Assert(e1 == nil && e2 == nil) 39 40 for i := 0; baseNameHrw == newNameHrw; i++ { 41 newName = newNamePrefix + strconv.Itoa(i) 42 newNameHrw, e1 = smap.HrwName2T(cbck.MakeUname(newName)) 43 cos.AssertNoErr(e1) 44 } 45 return newName 46 } 47 48 func GenerateNonexistentBucketName(prefix string, bp api.BaseParams) (string, error) { 49 for range 100 { 50 bck := cmn.Bck{ 51 Name: prefix + trand.String(8), 52 Provider: apc.AIS, 53 } 54 _, err := api.HeadBucket(bp, bck, true /* don't add to cluster MD */) 55 if err == nil { 56 continue 57 } 58 errHTTP, ok := err.(*cmn.ErrHTTP) 59 if !ok { 60 return "", 61 fmt.Errorf("error generating bucket name: expected error of type *cmn.ErrHTTP, but got: %T", err) 62 } 63 if errHTTP.Status == http.StatusNotFound { 64 return bck.Name, nil 65 } 66 67 return "", fmt.Errorf("error generating bucket name: unexpected HEAD request error: %v", err) 68 } 69 70 return "", errors.New("error generating bucket name: too many tries gave no result") 71 } 72 73 func BucketsContain(bcks cmn.Bcks, qbck cmn.QueryBcks) bool { 74 for i := range bcks { 75 bck := bcks[i] 76 if qbck.Equal(&bck) || qbck.Contains(&bck) { 77 return true 78 } 79 } 80 return false 81 } 82 83 func BucketExists(tb testing.TB, proxyURL string, bck cmn.Bck) (bool, error) { 84 if bck.IsQuery() { 85 if tb == nil { 86 return false, fmt.Errorf("expecting a named bucket, got %q", bck) 87 } 88 tassert.CheckFatal(tb, fmt.Errorf("expecting a named bucket, got %q", bck)) 89 } 90 bp := api.BaseParams{Client: gctx.Client, URL: proxyURL, Token: LoggedUserToken} 91 _, err := api.HeadBucket(bp, bck, true /*dontAddRemote*/) 92 if err == nil { 93 return true, nil 94 } 95 errHTTP, ok := err.(*cmn.ErrHTTP) 96 if !ok { 97 err = fmt.Errorf("expected an error of the type *cmn.ErrHTTP, got %v(%T)", err, err) 98 if tb == nil { 99 return false, err 100 } 101 tassert.CheckFatal(tb, err) 102 } 103 if errHTTP.Status != http.StatusNotFound { 104 err = fmt.Errorf("tools.BucketExists: expected status 404 (NotFound), got [%s]", errHTTP.Message) 105 if tb != nil { 106 tassert.CheckFatal(tb, err) 107 } else { 108 cos.Exitf("%v", err) 109 } 110 } 111 return false, err 112 } 113 114 func isRemoteBucket(tb testing.TB, proxyURL string, bck cmn.Bck) bool { 115 if !bck.IsRemote() { 116 return false 117 } 118 exists, err := BucketExists(tb, proxyURL, bck) 119 tassert.CheckFatal(tb, err) 120 return exists 121 } 122 123 func isCloudBucket(tb testing.TB, proxyURL string, bck cmn.Bck) bool { 124 if !bck.IsCloud() { 125 return false 126 } 127 exists, err := BucketExists(tb, proxyURL, bck) 128 tassert.CheckFatal(tb, err) 129 return exists 130 } 131 132 func PutObjRR(bp api.BaseParams, bck cmn.Bck, objName string, objSize int64, cksumType string) error { 133 reader, err := readers.NewRand(objSize, cksumType) 134 if err != nil { 135 return err 136 } 137 putArgs := api.PutArgs{ 138 BaseParams: bp, 139 Bck: bck, 140 ObjName: objName, 141 Cksum: reader.Cksum(), 142 Reader: reader, 143 } 144 _, err = api.PutObject(&putArgs) 145 return err 146 } 147 148 func PutRR(tb testing.TB, bp api.BaseParams, objSize int64, cksumType string, 149 bck cmn.Bck, dir string, objCount int) []string { 150 objNames := make([]string, objCount) 151 for i := range objCount { 152 fname := trand.String(20) 153 objName := filepath.Join(dir, fname) 154 objNames[i] = objName 155 // FIXME: Separate RandReader per object created inside PutObjRR to workaround 156 // https://github.com/golang/go/issues/30597 157 err := PutObjRR(bp, bck, objName, objSize, cksumType) 158 tassert.CheckFatal(tb, err) 159 } 160 161 return objNames 162 } 163 164 func isClusterK8s() (isK8s bool, err error) { 165 // NOTE: The test suite doesn't have to be deployed on K8s, the cluster has to be. 166 _, err = api.ETLList(BaseAPIParams(GetPrimaryURL())) 167 isK8s = err == nil 168 // HACK: Check based on error message. Unfortunately, there is no relevant HTTP code. 169 if err != nil && strings.Contains(err.Error(), "requires Kubernetes") { 170 err = nil 171 } 172 return 173 } 174 175 func isClusterLocal() (isLocal bool, err error) { 176 var ( 177 primaryURL = GetPrimaryURL() 178 smap *meta.Smap 179 bp = BaseAPIParams(primaryURL) 180 config *cmn.Config 181 fileData []byte 182 ) 183 if smap, err = api.GetClusterMap(bp); err != nil { 184 return 185 } 186 if config, err = api.GetDaemonConfig(bp, smap.Primary); err != nil { 187 return 188 } 189 fileData, err = os.ReadFile(filepath.Join(config.ConfigDir, fname.ProxyID)) 190 if err != nil { 191 if os.IsNotExist(err) { 192 err = nil 193 } 194 return 195 } 196 197 isLocal = strings.TrimSpace(string(fileData)) == smap.Primary.ID() 198 return 199 }