github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/fs/fqn_test.go (about) 1 // Package fs_test provides tests for fs package 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package fs_test 6 7 import ( 8 "os" 9 "strings" 10 "testing" 11 12 "github.com/NVIDIA/aistore/api/apc" 13 "github.com/NVIDIA/aistore/cmn" 14 "github.com/NVIDIA/aistore/cmn/cos" 15 "github.com/NVIDIA/aistore/core/mock" 16 "github.com/NVIDIA/aistore/fs" 17 "github.com/NVIDIA/aistore/tools/tassert" 18 ) 19 20 func TestParseFQN(t *testing.T) { 21 const tmpMpath = "/tmp/ais-fqn-test" 22 tests := []struct { 23 testName string 24 fqn string 25 mpaths []string 26 wantMPath string 27 wantBck cmn.Bck 28 wantContentType string 29 wantObjName string 30 wantErr bool 31 wantAddErr bool 32 }{ 33 // good 34 { 35 "smoke test", 36 tmpMpath + "/@ais/#namespace/bucket/%ob/objname", 37 []string{tmpMpath}, 38 tmpMpath, 39 cmn.Bck{Name: "bucket", Provider: apc.AIS, Ns: cmn.Ns{Name: "namespace"}}, 40 fs.ObjectType, "objname", false, 41 false, 42 }, 43 { 44 "smoke test (namespace global)", 45 tmpMpath + "/@ais/bucket/%ob/objname", 46 []string{tmpMpath}, 47 tmpMpath, 48 cmn.Bck{Name: "bucket", Provider: apc.AIS, Ns: cmn.NsGlobal}, 49 fs.ObjectType, "objname", false, 50 false, 51 }, 52 { 53 "content type (work)", 54 tmpMpath + "/@aws/bucket/%wk/objname", 55 []string{tmpMpath}, 56 tmpMpath, 57 cmn.Bck{Name: "bucket", Provider: apc.AWS, Ns: cmn.NsGlobal}, 58 fs.WorkfileType, "objname", false, 59 false, 60 }, 61 { 62 "cloud as bucket type (aws)", 63 tmpMpath + "/@aws/bucket/%ob/objname", 64 []string{tmpMpath}, 65 tmpMpath, 66 cmn.Bck{Name: "bucket", Provider: apc.AWS, Ns: cmn.NsGlobal}, 67 fs.ObjectType, "objname", false, 68 false, 69 }, 70 { 71 "cloud as bucket type (gcp)", 72 tmpMpath + "/@gcp/bucket/%ob/objname", 73 []string{tmpMpath}, 74 tmpMpath, 75 cmn.Bck{Name: "bucket", Provider: apc.GCP, Ns: cmn.NsGlobal}, 76 fs.ObjectType, "objname", false, 77 false, 78 }, 79 { 80 "non-empty namespace", 81 tmpMpath + "/@ais/#namespace/bucket/%ob/objname", 82 []string{tmpMpath}, 83 tmpMpath, 84 cmn.Bck{Name: "bucket", Provider: apc.AIS, Ns: cmn.Ns{Name: "namespace"}}, 85 fs.ObjectType, "objname", false, 86 false, 87 }, 88 { 89 "cloud namespace", 90 tmpMpath + "/@ais/@uuid#namespace/bucket/%ob/objname", 91 []string{tmpMpath}, 92 tmpMpath, 93 cmn.Bck{Name: "bucket", Provider: apc.AIS, Ns: cmn.Ns{UUID: "uuid", Name: "namespace"}}, 94 fs.ObjectType, "objname", false, 95 false, 96 }, 97 { 98 "long mount path name", 99 tmpMpath + "/super/long/@aws/bucket/%ob/objname", 100 []string{tmpMpath + "/super/long"}, 101 tmpMpath + "/super/long", 102 cmn.Bck{Name: "bucket", Provider: apc.AWS, Ns: cmn.NsGlobal}, 103 fs.ObjectType, "objname", false, 104 false, 105 }, 106 { 107 "long mount path name and objname in folder", 108 tmpMpath + "/super/long/@aws/bucket/%ob/folder/objname", 109 []string{tmpMpath + "/super/long"}, 110 tmpMpath + "/super/long", 111 cmn.Bck{Name: "bucket", Provider: apc.AWS, Ns: cmn.NsGlobal}, 112 fs.ObjectType, "folder/objname", false, 113 false, 114 }, 115 116 // bad 117 { 118 "nested mountpaths", 119 tmpMpath + "/super/long/long/@aws/bucket/%ob/folder/objname", 120 []string{"/super/long", "/super/long/long"}, 121 "", 122 cmn.Bck{Name: "bucket", Provider: apc.AWS, Ns: cmn.NsGlobal}, 123 fs.ObjectType, "folder/objname", true, 124 true, 125 }, 126 { 127 "too short name", 128 tmpMpath + "/bucket/objname", 129 []string{tmpMpath}, 130 "", 131 cmn.Bck{}, 132 "", "", true, 133 false, 134 }, 135 { 136 "invalid content type (not prefixed with '%')", 137 tmpMpath + "/@gcp/bucket/ob/objname", 138 []string{tmpMpath}, 139 "", 140 cmn.Bck{}, 141 "", "", true, 142 false, 143 }, 144 { 145 "invalid content type (empty)", 146 tmpMpath + "/@ais/bucket/name", 147 []string{tmpMpath}, 148 "", 149 cmn.Bck{}, 150 "", "", true, 151 false, 152 }, 153 { 154 "invalid content type (unknown)", 155 tmpMpath + "/@gcp/bucket/%un/objname", 156 []string{tmpMpath}, 157 "", 158 cmn.Bck{}, 159 "", "", true, 160 false, 161 }, 162 { 163 "empty bucket name", 164 tmpMpath + "/@ais//%ob/objname", 165 []string{tmpMpath}, 166 "", 167 cmn.Bck{}, 168 "", "", true, 169 false, 170 }, 171 { 172 "empty bucket name (without slash)", 173 tmpMpath + "/@ais/%ob/objname", 174 []string{tmpMpath}, 175 "", 176 cmn.Bck{}, 177 "", "", true, 178 false, 179 }, 180 { 181 "empty object name", 182 tmpMpath + "/@ais/bucket/%ob/", 183 []string{tmpMpath}, 184 "", 185 cmn.Bck{}, 186 "", "", true, 187 false, 188 }, 189 { 190 "empty backend provider", 191 tmpMpath + "/bucket/%ob/objname", 192 []string{tmpMpath}, 193 "", 194 cmn.Bck{}, 195 "", "", true, 196 false, 197 }, 198 { 199 "invalid backend provider (not prefixed with '@')", 200 tmpMpath + "/gcp/bucket/%ob/objname", 201 []string{tmpMpath}, 202 "", 203 cmn.Bck{}, 204 "", "", true, 205 false, 206 }, 207 { 208 "invalid backend provider (unknown)", 209 tmpMpath + "/@unknown/bucket/%ob/objname", 210 []string{tmpMpath}, 211 "", 212 cmn.Bck{}, 213 "", "", true, 214 false, 215 }, 216 { 217 "invalid backend provider (cloud)", 218 tmpMpath + "/@cloud/bucket/%ob/objname", 219 []string{tmpMpath}, 220 "", 221 cmn.Bck{}, 222 "", "", true, 223 false, 224 }, 225 { 226 "invalid cloud namespace", 227 tmpMpath + "/@cloud/@uuid/bucket/%ob/objname", 228 []string{tmpMpath}, 229 "", 230 cmn.Bck{}, 231 "", "", true, 232 false, 233 }, 234 { 235 "no matching mountpath", 236 tmpMpath + "/@ais/bucket/%obj/objname", 237 []string{tmpMpath + "/a", tmpMpath + "/b"}, 238 "", 239 cmn.Bck{}, 240 "", "", true, 241 false, 242 }, 243 { 244 "fqn is mpath", 245 tmpMpath + "/mpath", 246 []string{tmpMpath + "/mpath"}, 247 "", 248 cmn.Bck{}, 249 "", "", true, 250 false, 251 }, 252 } 253 254 for i := range tests { 255 tt := tests[i] 256 t.Run(tt.testName, func(t *testing.T) { 257 mios := mock.NewIOS() 258 fs.TestNew(mios) 259 260 for _, mpath := range tt.mpaths { 261 if err := cos.Stat(mpath); os.IsNotExist(err) { 262 cos.CreateDir(mpath) 263 defer os.RemoveAll(mpath) 264 } 265 _, err := fs.Add(mpath, "daeID") 266 if err != nil && !tt.wantAddErr { 267 tassert.CheckFatal(t, err) 268 } 269 } 270 fs.CSM.Reg(fs.ObjectType, &fs.ObjectContentResolver{}, true) 271 fs.CSM.Reg(fs.WorkfileType, &fs.WorkfileContentResolver{}, true) 272 273 var parsed fs.ParsedFQN 274 err := parsed.Init(tt.fqn) 275 if (err != nil) != tt.wantErr { 276 t.Errorf("fqn2info() error = %v, wantErr %v", err, tt.wantErr) 277 return 278 } 279 if err != nil { 280 return 281 } 282 var ( 283 gotMpath, gotBck = parsed.Mountpath.Path, parsed.Bck 284 gotContentType, gotObjName = parsed.ContentType, parsed.ObjName 285 ) 286 if gotMpath != tt.wantMPath { 287 t.Errorf("gotMpath = %v, want %v", gotMpath, tt.wantMPath) 288 } 289 if !gotBck.Equal(&tt.wantBck) { 290 t.Errorf("gotBck = %v, want %v", gotBck, tt.wantBck) 291 } 292 if gotContentType != tt.wantContentType { 293 t.Errorf("gotContentType = %v, want %v", gotContentType, tt.wantContentType) 294 } 295 if gotObjName != tt.wantObjName { 296 t.Errorf("gotObjName = %v, want %v", gotObjName, tt.wantObjName) 297 } 298 }) 299 } 300 } 301 302 func TestMakeAndParseFQN(t *testing.T) { 303 tests := []struct { 304 mpath string 305 bck cmn.Bck 306 contentType string 307 objName string 308 }{ 309 { 310 mpath: "/tmp/path", 311 bck: cmn.Bck{ 312 Name: "bucket", 313 Provider: apc.AIS, 314 Ns: cmn.NsGlobal, 315 }, 316 contentType: fs.ObjectType, 317 objName: "object/name", 318 }, 319 { 320 mpath: "/tmp/path", 321 bck: cmn.Bck{ 322 Name: "bucket", 323 Provider: apc.AWS, 324 Ns: cmn.Ns{UUID: "uuid", Name: "namespace"}, 325 }, 326 contentType: fs.WorkfileType, 327 objName: "object/name", 328 }, 329 { 330 mpath: "/tmp/path", 331 bck: cmn.Bck{ 332 Name: "bucket", 333 Provider: apc.AWS, 334 Ns: cmn.Ns{Name: "alias"}, 335 }, 336 contentType: fs.ObjectType, 337 objName: "object/name", 338 }, 339 { 340 mpath: "/tmp/path", 341 bck: cmn.Bck{ 342 Name: "bucket", 343 Provider: apc.GCP, 344 Ns: cmn.NsGlobal, 345 }, 346 contentType: fs.ObjectType, 347 objName: "object/name", 348 }, 349 } 350 351 for i := range tests { 352 tt := tests[i] 353 t.Run(strings.Join([]string{tt.mpath, tt.bck.String(), tt.contentType, tt.objName}, "|"), func(t *testing.T) { 354 mios := mock.NewIOS() 355 fs.TestNew(mios) 356 357 if err := cos.Stat(tt.mpath); os.IsNotExist(err) { 358 cos.CreateDir(tt.mpath) 359 defer os.RemoveAll(tt.mpath) 360 } 361 _, err := fs.Add(tt.mpath, "daeID") 362 tassert.CheckFatal(t, err) 363 364 fs.CSM.Reg(fs.ObjectType, &fs.ObjectContentResolver{}, true) 365 fs.CSM.Reg(fs.WorkfileType, &fs.WorkfileContentResolver{}, true) 366 367 mpaths := fs.GetAvail() 368 fqn := mpaths[tt.mpath].MakePathFQN(&tt.bck, tt.contentType, tt.objName) 369 370 var parsed fs.ParsedFQN 371 err = parsed.Init(fqn) 372 if err != nil { 373 t.Fatalf("failed to parse FQN: %v", err) 374 } 375 var ( 376 gotMpath, gotBck = parsed.Mountpath.Path, parsed.Bck 377 gotContentType, gotObjName = parsed.ContentType, parsed.ObjName 378 ) 379 if gotMpath != tt.mpath { 380 t.Errorf("gotMpath = %v, want %v", gotMpath, tt.mpath) 381 } 382 if gotBck != tt.bck { 383 t.Errorf("gotBck = %v, want %v", gotBck, tt.bck) 384 } 385 if gotContentType != tt.contentType { 386 t.Errorf("getContentType = %v, want %v", gotContentType, tt.contentType) 387 } 388 if gotObjName != tt.objName { 389 t.Errorf("gotObjName = %v, want %v", gotObjName, tt.objName) 390 } 391 }) 392 } 393 } 394 395 func BenchmarkParseFQN(b *testing.B) { 396 var ( 397 mpath = "/tmp/mpath" 398 mios = mock.NewIOS() 399 bck = cmn.Bck{Name: "bucket", Provider: apc.AIS, Ns: cmn.NsGlobal} 400 ) 401 402 fs.TestNew(mios) 403 cos.CreateDir(mpath) 404 defer os.RemoveAll(mpath) 405 fs.Add(mpath, "daeID") 406 fs.CSM.Reg(fs.ObjectType, &fs.ObjectContentResolver{}) 407 408 mpaths := fs.GetAvail() 409 fqn := mpaths[mpath].MakePathFQN(&bck, fs.ObjectType, "super/long/name") 410 b.ResetTimer() 411 412 for range b.N { 413 var parsed fs.ParsedFQN 414 parsed.Init(fqn) 415 } 416 }