storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/data-usage_test.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2020 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "bytes" 21 "context" 22 "fmt" 23 "io/ioutil" 24 "os" 25 "path" 26 "path/filepath" 27 "testing" 28 ) 29 30 type usageTestFile struct { 31 name string 32 size int 33 } 34 35 func TestDataUsageUpdate(t *testing.T) { 36 base, err := ioutil.TempDir("", "TestDataUsageUpdate") 37 if err != nil { 38 t.Skip(err) 39 } 40 const bucket = "bucket" 41 defer os.RemoveAll(base) 42 var files = []usageTestFile{ 43 {name: "rootfile", size: 10000}, 44 {name: "rootfile2", size: 10000}, 45 {name: "dir1/d1file", size: 2000}, 46 {name: "dir2/d2file", size: 300}, 47 {name: "dir1/dira/dafile", size: 100000}, 48 {name: "dir1/dira/dbfile", size: 200000}, 49 {name: "dir1/dira/dirasub/dcfile", size: 1000000}, 50 {name: "dir1/dira/dirasub/sublevel3/dccccfile", size: 10}, 51 } 52 createUsageTestFiles(t, base, bucket, files) 53 54 getSize := func(item scannerItem) (sizeS sizeSummary, err error) { 55 if item.Typ&os.ModeDir == 0 { 56 var s os.FileInfo 57 s, err = os.Stat(item.Path) 58 if err != nil { 59 return 60 } 61 sizeS.totalSize = s.Size() 62 return sizeS, nil 63 } 64 return 65 } 66 67 got, err := scanDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize) 68 if err != nil { 69 t.Fatal(err) 70 } 71 72 // Test dirs 73 var want = []struct { 74 path string 75 isNil bool 76 size, objs int 77 flatten bool 78 oSizes sizeHistogram 79 }{ 80 { 81 path: "/", 82 size: 1322310, 83 flatten: true, 84 objs: 8, 85 oSizes: sizeHistogram{0: 2, 1: 6}, 86 }, 87 { 88 path: "/", 89 size: 20000, 90 objs: 2, 91 oSizes: sizeHistogram{1: 2}, 92 }, 93 { 94 path: "/dir1", 95 size: 2000, 96 objs: 1, 97 oSizes: sizeHistogram{1: 1}, 98 }, 99 { 100 path: "/dir1/dira", 101 flatten: true, 102 size: 1300010, 103 objs: 4, 104 oSizes: sizeHistogram{0: 1, 1: 3}, 105 }, 106 { 107 path: "/dir1/dira/", 108 flatten: true, 109 size: 1300010, 110 objs: 4, 111 oSizes: sizeHistogram{0: 1, 1: 3}, 112 }, 113 { 114 path: "/dir1", 115 size: 2000, 116 objs: 1, 117 oSizes: sizeHistogram{0: 0, 1: 1}, 118 }, 119 { 120 // Children are flattened 121 path: "/dir1/dira/", 122 size: 1300010, 123 objs: 4, 124 oSizes: sizeHistogram{0: 1, 1: 3}, 125 }, 126 { 127 path: "/nonexistying", 128 isNil: true, 129 }, 130 } 131 132 for _, w := range want { 133 p := path.Join(bucket, w.path) 134 t.Run(p, func(t *testing.T) { 135 e := got.find(p) 136 if w.isNil { 137 if e != nil { 138 t.Error("want nil, got", e) 139 } 140 return 141 } 142 if e == nil { 143 t.Fatal("got nil result") 144 } 145 t.Log(e.Children) 146 if w.flatten { 147 *e = got.flatten(*e) 148 } 149 if e.Size != int64(w.size) { 150 t.Error("got size", e.Size, "want", w.size) 151 } 152 if e.Objects != uint64(w.objs) { 153 t.Error("got objects", e.Objects, "want", w.objs) 154 } 155 if e.ObjSizes != w.oSizes { 156 t.Error("got histogram", e.ObjSizes, "want", w.oSizes) 157 } 158 }) 159 } 160 161 files = []usageTestFile{ 162 { 163 name: "newfolder/afile", 164 size: 4, 165 }, 166 { 167 name: "newfolder/anotherone", 168 size: 1, 169 }, 170 { 171 name: "newfolder/anemptyone", 172 size: 0, 173 }, 174 { 175 name: "dir1/fileindir1", 176 size: 20000, 177 }, 178 { 179 name: "dir1/dirc/fileindirc", 180 size: 20000, 181 }, 182 { 183 name: "rootfile3", 184 size: 1000, 185 }, 186 } 187 createUsageTestFiles(t, base, bucket, files) 188 got, err = scanDataFolder(context.Background(), base, got, getSize) 189 if err != nil { 190 t.Fatal(err) 191 } 192 193 want = []struct { 194 path string 195 isNil bool 196 size, objs int 197 flatten bool 198 oSizes sizeHistogram 199 }{ 200 { 201 path: "/", 202 size: 1363315, 203 flatten: true, 204 objs: 14, 205 oSizes: sizeHistogram{0: 6, 1: 8}, 206 }, 207 { 208 path: "/", 209 size: 21000, 210 objs: 3, 211 oSizes: sizeHistogram{0: 1, 1: 2}, 212 }, 213 { 214 path: "/newfolder", 215 size: 5, 216 objs: 3, 217 oSizes: sizeHistogram{0: 3}, 218 }, 219 { 220 path: "/dir1/dira", 221 size: 1300010, 222 flatten: true, 223 objs: 4, 224 oSizes: sizeHistogram{0: 1, 1: 3}, 225 }, 226 { 227 path: "/nonexistying", 228 isNil: true, 229 }, 230 } 231 232 for _, w := range want { 233 t.Run(w.path, func(t *testing.T) { 234 e := got.find(path.Join(bucket, w.path)) 235 if w.isNil { 236 if e != nil { 237 t.Error("want nil, got", e) 238 } 239 return 240 } 241 if e == nil { 242 t.Fatal("got nil result") 243 } 244 if w.flatten { 245 *e = got.flatten(*e) 246 } 247 if e.Size != int64(w.size) { 248 t.Error("got size", e.Size, "want", w.size) 249 } 250 if e.Objects != uint64(w.objs) { 251 t.Error("got objects", e.Objects, "want", w.objs) 252 } 253 if e.ObjSizes != w.oSizes { 254 t.Error("got histogram", e.ObjSizes, "want", w.oSizes) 255 } 256 }) 257 } 258 259 files = []usageTestFile{ 260 { 261 name: "dir1/dira/dirasub/fileindira2", 262 size: 200, 263 }, 264 } 265 266 createUsageTestFiles(t, base, bucket, files) 267 err = os.RemoveAll(filepath.Join(base, bucket, "dir1/dira/dirasub/dcfile")) 268 if err != nil { 269 t.Fatal(err) 270 } 271 // Changed dir must be picked up in this many cycles. 272 for i := 0; i < dataUsageUpdateDirCycles; i++ { 273 got, err = scanDataFolder(context.Background(), base, got, getSize) 274 if err != nil { 275 t.Fatal(err) 276 } 277 } 278 279 want = []struct { 280 path string 281 isNil bool 282 size, objs int 283 flatten bool 284 oSizes sizeHistogram 285 }{ 286 { 287 path: "/", 288 size: 363515, 289 flatten: true, 290 objs: 14, 291 oSizes: sizeHistogram{0: 7, 1: 7}, 292 }, 293 { 294 path: "/dir1/dira", 295 size: 300210, 296 objs: 4, 297 flatten: true, 298 oSizes: sizeHistogram{0: 2, 1: 2}, 299 }, 300 } 301 302 for _, w := range want { 303 p := path.Join(bucket, w.path) 304 t.Run(p, func(t *testing.T) { 305 e := got.find(p) 306 if w.isNil { 307 if e != nil { 308 t.Error("want nil, got", e) 309 } 310 return 311 } 312 if e == nil { 313 t.Fatal("got nil result") 314 } 315 if w.flatten { 316 *e = got.flatten(*e) 317 } 318 if e.Size != int64(w.size) { 319 t.Error("got size", e.Size, "want", w.size) 320 } 321 if e.Objects != uint64(w.objs) { 322 t.Error("got objects", e.Objects, "want", w.objs) 323 } 324 if e.ObjSizes != w.oSizes { 325 t.Error("got histogram", e.ObjSizes, "want", w.oSizes) 326 } 327 }) 328 } 329 } 330 331 func TestDataUsageUpdatePrefix(t *testing.T) { 332 base, err := ioutil.TempDir("", "TestDataUpdateUsagePrefix") 333 if err != nil { 334 t.Skip(err) 335 } 336 base = filepath.Join(base, "bucket") 337 defer os.RemoveAll(base) 338 var files = []usageTestFile{ 339 {name: "bucket/rootfile", size: 10000}, 340 {name: "bucket/rootfile2", size: 10000}, 341 {name: "bucket/dir1/d1file", size: 2000}, 342 {name: "bucket/dir2/d2file", size: 300}, 343 {name: "bucket/dir1/dira/dafile", size: 100000}, 344 {name: "bucket/dir1/dira/dbfile", size: 200000}, 345 {name: "bucket/dir1/dira/dirasub/dcfile", size: 1000000}, 346 {name: "bucket/dir1/dira/dirasub/sublevel3/dccccfile", size: 10}, 347 } 348 createUsageTestFiles(t, base, "", files) 349 350 getSize := func(item scannerItem) (sizeS sizeSummary, err error) { 351 if item.Typ&os.ModeDir == 0 { 352 var s os.FileInfo 353 s, err = os.Stat(item.Path) 354 if err != nil { 355 return 356 } 357 sizeS.totalSize = s.Size() 358 return 359 } 360 return 361 } 362 got, err := scanDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize) 363 if err != nil { 364 t.Fatal(err) 365 } 366 if got.root() == nil { 367 t.Log("cached folders:") 368 for folder := range got.Cache { 369 t.Log("folder:", folder) 370 } 371 t.Fatal("got nil root.") 372 } 373 374 // Test dirs 375 var want = []struct { 376 path string 377 isNil bool 378 size, objs int 379 oSizes sizeHistogram 380 }{ 381 { 382 path: "flat", 383 size: 1322310, 384 objs: 8, 385 oSizes: sizeHistogram{0: 2, 1: 6}, 386 }, 387 { 388 path: "bucket/", 389 size: 20000, 390 objs: 2, 391 oSizes: sizeHistogram{1: 2}, 392 }, 393 { 394 path: "bucket/dir1", 395 size: 2000, 396 objs: 1, 397 oSizes: sizeHistogram{1: 1}, 398 }, 399 { 400 path: "bucket/dir1/dira", 401 size: 1300010, 402 objs: 4, 403 oSizes: sizeHistogram{0: 1, 1: 3}, 404 }, 405 { 406 path: "bucket/dir1/dira/", 407 size: 1300010, 408 objs: 4, 409 oSizes: sizeHistogram{0: 1, 1: 3}, 410 }, 411 { 412 path: "bucket/nonexistying", 413 isNil: true, 414 }, 415 } 416 417 for _, w := range want { 418 t.Run(w.path, func(t *testing.T) { 419 e := got.find(w.path) 420 if w.path == "flat" { 421 f := got.flatten(*got.root()) 422 e = &f 423 } 424 if w.isNil { 425 if e != nil { 426 t.Error("want nil, got", e) 427 } 428 return 429 } 430 if e == nil { 431 t.Fatal("got nil result") 432 } 433 if e.Size != int64(w.size) { 434 t.Error("got size", e.Size, "want", w.size) 435 } 436 if e.Objects != uint64(w.objs) { 437 t.Error("got objects", e.Objects, "want", w.objs) 438 } 439 if e.ObjSizes != w.oSizes { 440 t.Error("got histogram", e.ObjSizes, "want", w.oSizes) 441 } 442 }) 443 } 444 445 files = []usageTestFile{ 446 { 447 name: "bucket/newfolder/afile", 448 size: 4, 449 }, 450 { 451 name: "bucket/newfolder/anotherone", 452 size: 1, 453 }, 454 { 455 name: "bucket/newfolder/anemptyone", 456 size: 0, 457 }, 458 { 459 name: "bucket/dir1/fileindir1", 460 size: 20000, 461 }, 462 { 463 name: "bucket/dir1/dirc/fileindirc", 464 size: 20000, 465 }, 466 { 467 name: "bucket/rootfile3", 468 size: 1000, 469 }, 470 } 471 createUsageTestFiles(t, base, "", files) 472 got, err = scanDataFolder(context.Background(), base, got, getSize) 473 if err != nil { 474 t.Fatal(err) 475 } 476 477 want = []struct { 478 path string 479 isNil bool 480 size, objs int 481 oSizes sizeHistogram 482 }{ 483 { 484 path: "flat", 485 size: 1363315, 486 objs: 14, 487 oSizes: sizeHistogram{0: 6, 1: 8}, 488 }, 489 { 490 path: "bucket/", 491 size: 21000, 492 objs: 3, 493 oSizes: sizeHistogram{0: 1, 1: 2}, 494 }, 495 { 496 path: "bucket/newfolder", 497 size: 5, 498 objs: 3, 499 oSizes: sizeHistogram{0: 3}, 500 }, 501 { 502 path: "bucket/dir1/dira", 503 size: 1300010, 504 objs: 4, 505 oSizes: sizeHistogram{0: 1, 1: 3}, 506 }, 507 { 508 path: "bucket/nonexistying", 509 isNil: true, 510 }, 511 } 512 513 for _, w := range want { 514 t.Run(w.path, func(t *testing.T) { 515 e := got.find(w.path) 516 if w.path == "flat" { 517 f := got.flatten(*got.root()) 518 e = &f 519 } 520 if w.isNil { 521 if e != nil { 522 t.Error("want nil, got", e) 523 } 524 return 525 } 526 if e == nil { 527 t.Fatal("got nil result") 528 } 529 if e.Size != int64(w.size) { 530 t.Error("got size", e.Size, "want", w.size) 531 } 532 if e.Objects != uint64(w.objs) { 533 t.Error("got objects", e.Objects, "want", w.objs) 534 } 535 if e.ObjSizes != w.oSizes { 536 t.Error("got histogram", e.ObjSizes, "want", w.oSizes) 537 } 538 }) 539 } 540 541 files = []usageTestFile{ 542 { 543 name: "bucket/dir1/dira/dirasub/fileindira2", 544 size: 200, 545 }, 546 } 547 548 createUsageTestFiles(t, base, "", files) 549 err = os.RemoveAll(filepath.Join(base, "bucket/dir1/dira/dirasub/dcfile")) 550 if err != nil { 551 t.Fatal(err) 552 } 553 // Changed dir must be picked up in this many cycles. 554 for i := 0; i < dataUsageUpdateDirCycles; i++ { 555 got, err = scanDataFolder(context.Background(), base, got, getSize) 556 if err != nil { 557 t.Fatal(err) 558 } 559 } 560 561 want = []struct { 562 path string 563 isNil bool 564 size, objs int 565 oSizes sizeHistogram 566 }{ 567 { 568 path: "flat", 569 size: 363515, 570 objs: 14, 571 oSizes: sizeHistogram{0: 7, 1: 7}, 572 }, 573 { 574 path: "bucket/dir1/dira", 575 size: 300210, 576 objs: 4, 577 oSizes: sizeHistogram{0: 2, 1: 2}, 578 }, 579 } 580 581 for _, w := range want { 582 t.Run(w.path, func(t *testing.T) { 583 e := got.find(w.path) 584 if w.path == "flat" { 585 f := got.flatten(*got.root()) 586 e = &f 587 } 588 if w.isNil { 589 if e != nil { 590 t.Error("want nil, got", e) 591 } 592 return 593 } 594 if e == nil { 595 t.Fatal("got nil result") 596 } 597 if e.Size != int64(w.size) { 598 t.Error("got size", e.Size, "want", w.size) 599 } 600 if e.Objects != uint64(w.objs) { 601 t.Error("got objects", e.Objects, "want", w.objs) 602 } 603 if e.ObjSizes != w.oSizes { 604 t.Error("got histogram", e.ObjSizes, "want", w.oSizes) 605 } 606 }) 607 } 608 } 609 610 func createUsageTestFiles(t *testing.T, base, bucket string, files []usageTestFile) { 611 for _, f := range files { 612 err := os.MkdirAll(filepath.Dir(filepath.Join(base, bucket, f.name)), os.ModePerm) 613 if err != nil { 614 t.Fatal(err) 615 } 616 err = ioutil.WriteFile(filepath.Join(base, bucket, f.name), make([]byte, f.size), os.ModePerm) 617 if err != nil { 618 t.Fatal(err) 619 } 620 } 621 } 622 623 func TestDataUsageCacheSerialize(t *testing.T) { 624 base, err := ioutil.TempDir("", "TestDataUsageCacheSerialize") 625 if err != nil { 626 t.Skip(err) 627 } 628 const bucket = "abucket" 629 defer os.RemoveAll(base) 630 var files = []usageTestFile{ 631 {name: "rootfile", size: 10000}, 632 {name: "rootfile2", size: 10000}, 633 {name: "dir1/d1file", size: 2000}, 634 {name: "dir2/d2file", size: 300}, 635 {name: "dir2/d2file2", size: 300}, 636 {name: "dir2/d2file3/", size: 300}, 637 {name: "dir2/d2file4/", size: 300}, 638 {name: "dir2/d2file5", size: 300}, 639 {name: "dir1/dira/dafile", size: 100000}, 640 {name: "dir1/dira/dbfile", size: 200000}, 641 {name: "dir1/dira/dirasub/dcfile", size: 1000000}, 642 {name: "dir1/dira/dirasub/sublevel3/dccccfile", size: 10}, 643 {name: "dir1/dira/dirasub/sublevel3/dccccfile20", size: 20}, 644 {name: "dir1/dira/dirasub/sublevel3/dccccfile30", size: 30}, 645 {name: "dir1/dira/dirasub/sublevel3/dccccfile40", size: 40}, 646 } 647 createUsageTestFiles(t, base, bucket, files) 648 649 getSize := func(item scannerItem) (sizeS sizeSummary, err error) { 650 if item.Typ&os.ModeDir == 0 { 651 var s os.FileInfo 652 s, err = os.Stat(item.Path) 653 if err != nil { 654 return 655 } 656 sizeS.totalSize = s.Size() 657 return 658 } 659 return 660 } 661 want, err := scanDataFolder(context.Background(), base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize) 662 if err != nil { 663 t.Fatal(err) 664 } 665 var buf bytes.Buffer 666 err = want.serializeTo(&buf) 667 if err != nil { 668 t.Fatal(err) 669 } 670 t.Log("serialized size:", buf.Len(), "bytes") 671 var got dataUsageCache 672 err = got.deserialize(&buf) 673 if err != nil { 674 t.Fatal(err) 675 } 676 if got.Info.LastUpdate.IsZero() { 677 t.Error("lastupdate not set") 678 } 679 680 if !want.Info.LastUpdate.Equal(got.Info.LastUpdate) { 681 t.Fatalf("deserialize LastUpdate mismatch\nwant: %+v\ngot: %+v", want, got) 682 } 683 if len(want.Cache) != len(got.Cache) { 684 t.Errorf("deserialize mismatch length\nwant: %+v\ngot: %+v", len(want.Cache), len(got.Cache)) 685 } 686 for wkey, wval := range want.Cache { 687 gotv := got.Cache[wkey] 688 if fmt.Sprint(gotv) != fmt.Sprint(wval) { 689 t.Errorf("deserialize mismatch, key %v\nwant: %+v\ngot: %+v", wkey, wval, gotv) 690 } 691 } 692 693 }