github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/format-erasure_test.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "crypto/sha256" 22 "encoding/hex" 23 "encoding/json" 24 "errors" 25 "os" 26 "reflect" 27 "testing" 28 ) 29 30 // tests fixFormatErasureV3 - fix format.json on all disks. 31 func TestFixFormatV3(t *testing.T) { 32 erasureDirs, err := getRandomDisks(8) 33 if err != nil { 34 t.Fatal(err) 35 } 36 for _, erasureDir := range erasureDirs { 37 defer os.RemoveAll(erasureDir) 38 } 39 endpoints := mustGetNewEndpoints(0, 8, erasureDirs...) 40 41 storageDisks, errs := initStorageDisksWithErrors(endpoints, storageOpts{cleanUp: false, healthCheck: false}) 42 for _, err := range errs { 43 if err != nil && err != errDiskNotFound { 44 t.Fatal(err) 45 } 46 } 47 48 format := newFormatErasureV3(1, 8) 49 format.Erasure.DistributionAlgo = formatErasureVersionV2DistributionAlgoV1 50 formats := make([]*formatErasureV3, 8) 51 52 for j := 0; j < 8; j++ { 53 newFormat := format.Clone() 54 newFormat.Erasure.This = format.Erasure.Sets[0][j] 55 formats[j] = newFormat 56 } 57 58 formats[1] = nil 59 expThis := formats[2].Erasure.This 60 formats[2].Erasure.This = "" 61 if err := fixFormatErasureV3(storageDisks, endpoints, formats); err != nil { 62 t.Fatal(err) 63 } 64 65 newFormats, errs := loadFormatErasureAll(storageDisks, false) 66 for _, err := range errs { 67 if err != nil && err != errUnformattedDisk { 68 t.Fatal(err) 69 } 70 } 71 gotThis := newFormats[2].Erasure.This 72 if expThis != gotThis { 73 t.Fatalf("expected uuid %s, got %s", expThis, gotThis) 74 } 75 } 76 77 // tests formatErasureV3ThisEmpty conditions. 78 func TestFormatErasureEmpty(t *testing.T) { 79 format := newFormatErasureV3(1, 16) 80 format.Erasure.DistributionAlgo = formatErasureVersionV2DistributionAlgoV1 81 formats := make([]*formatErasureV3, 16) 82 83 for j := 0; j < 16; j++ { 84 newFormat := format.Clone() 85 newFormat.Erasure.This = format.Erasure.Sets[0][j] 86 formats[j] = newFormat 87 } 88 89 // empty format to indicate disk not found, but this 90 // empty should return false. 91 formats[0] = nil 92 93 if ok := formatErasureV3ThisEmpty(formats); ok { 94 t.Fatalf("expected value false, got %t", ok) 95 } 96 97 formats[2].Erasure.This = "" 98 if ok := formatErasureV3ThisEmpty(formats); !ok { 99 t.Fatalf("expected value true, got %t", ok) 100 } 101 } 102 103 // Tests xl format migration. 104 func TestFormatErasureMigrate(t *testing.T) { 105 // Get test root. 106 rootPath := t.TempDir() 107 108 m := &formatErasureV1{} 109 m.Format = formatBackendErasure 110 m.Version = formatMetaVersionV1 111 m.Erasure.Version = formatErasureVersionV1 112 m.Erasure.Disk = mustGetUUID() 113 m.Erasure.JBOD = []string{m.Erasure.Disk, mustGetUUID(), mustGetUUID(), mustGetUUID()} 114 115 b, err := json.Marshal(m) 116 if err != nil { 117 t.Fatal(err) 118 } 119 120 if err = os.MkdirAll(pathJoin(rootPath, minioMetaBucket), os.FileMode(0o755)); err != nil { 121 t.Fatal(err) 122 } 123 124 if err = os.WriteFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile), b, os.FileMode(0o644)); err != nil { 125 t.Fatal(err) 126 } 127 128 formatData, _, err := formatErasureMigrate(rootPath) 129 if err != nil { 130 t.Fatal(err) 131 } 132 133 migratedVersion, err := formatGetBackendErasureVersion(formatData) 134 if err != nil { 135 t.Fatal(err) 136 } 137 138 if migratedVersion != formatErasureVersionV3 { 139 t.Fatalf("expected version: %s, got: %s", formatErasureVersionV3, migratedVersion) 140 } 141 142 b, err = os.ReadFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile)) 143 if err != nil { 144 t.Fatal(err) 145 } 146 formatV3 := &formatErasureV3{} 147 if err = json.Unmarshal(b, formatV3); err != nil { 148 t.Fatal(err) 149 } 150 if formatV3.Erasure.This != m.Erasure.Disk { 151 t.Fatalf("expected drive uuid: %s, got: %s", m.Erasure.Disk, formatV3.Erasure.This) 152 } 153 if len(formatV3.Erasure.Sets) != 1 { 154 t.Fatalf("expected single set after migrating from v1 to v3, but found %d", len(formatV3.Erasure.Sets)) 155 } 156 if !reflect.DeepEqual(formatV3.Erasure.Sets[0], m.Erasure.JBOD) { 157 t.Fatalf("expected drive uuid: %v, got: %v", m.Erasure.JBOD, formatV3.Erasure.Sets[0]) 158 } 159 160 m = &formatErasureV1{} 161 m.Format = "unknown" 162 m.Version = formatMetaVersionV1 163 m.Erasure.Version = formatErasureVersionV1 164 m.Erasure.Disk = mustGetUUID() 165 m.Erasure.JBOD = []string{m.Erasure.Disk, mustGetUUID(), mustGetUUID(), mustGetUUID()} 166 167 b, err = json.Marshal(m) 168 if err != nil { 169 t.Fatal(err) 170 } 171 172 if err = os.WriteFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile), b, os.FileMode(0o644)); err != nil { 173 t.Fatal(err) 174 } 175 176 if _, _, err = formatErasureMigrate(rootPath); err == nil { 177 t.Fatal("Expected to fail with unexpected backend format") 178 } 179 180 m = &formatErasureV1{} 181 m.Format = formatBackendErasure 182 m.Version = formatMetaVersionV1 183 m.Erasure.Version = "30" 184 m.Erasure.Disk = mustGetUUID() 185 m.Erasure.JBOD = []string{m.Erasure.Disk, mustGetUUID(), mustGetUUID(), mustGetUUID()} 186 187 b, err = json.Marshal(m) 188 if err != nil { 189 t.Fatal(err) 190 } 191 192 if err = os.WriteFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile), b, os.FileMode(0o644)); err != nil { 193 t.Fatal(err) 194 } 195 196 if _, _, err = formatErasureMigrate(rootPath); err == nil { 197 t.Fatal("Expected to fail with unexpected backend format version number") 198 } 199 } 200 201 // Tests check format xl value. 202 func TestCheckFormatErasureValue(t *testing.T) { 203 testCases := []struct { 204 format *formatErasureV3 205 success bool 206 }{ 207 // Invalid Erasure format version "2". 208 { 209 &formatErasureV3{ 210 formatMetaV1: formatMetaV1{ 211 Version: "2", 212 Format: "Erasure", 213 }, 214 Erasure: struct { 215 Version string `json:"version"` 216 This string `json:"this"` 217 Sets [][]string `json:"sets"` 218 DistributionAlgo string `json:"distributionAlgo"` 219 }{ 220 Version: "2", 221 }, 222 }, 223 false, 224 }, 225 // Invalid Erasure format "Unknown". 226 { 227 &formatErasureV3{ 228 formatMetaV1: formatMetaV1{ 229 Version: "1", 230 Format: "Unknown", 231 }, 232 Erasure: struct { 233 Version string `json:"version"` 234 This string `json:"this"` 235 Sets [][]string `json:"sets"` 236 DistributionAlgo string `json:"distributionAlgo"` 237 }{ 238 Version: "2", 239 }, 240 }, 241 false, 242 }, 243 // Invalid Erasure format version "0". 244 { 245 &formatErasureV3{ 246 formatMetaV1: formatMetaV1{ 247 Version: "1", 248 Format: "Erasure", 249 }, 250 Erasure: struct { 251 Version string `json:"version"` 252 This string `json:"this"` 253 Sets [][]string `json:"sets"` 254 DistributionAlgo string `json:"distributionAlgo"` 255 }{ 256 Version: "0", 257 }, 258 }, 259 false, 260 }, 261 } 262 263 // Valid all test cases. 264 for i, testCase := range testCases { 265 if err := checkFormatErasureValue(testCase.format, nil); err != nil && testCase.success { 266 t.Errorf("Test %d: Expected failure %s", i+1, err) 267 } 268 } 269 } 270 271 // Tests getFormatErasureInQuorum() 272 func TestGetFormatErasureInQuorumCheck(t *testing.T) { 273 setCount := 2 274 setDriveCount := 16 275 276 format := newFormatErasureV3(setCount, setDriveCount) 277 format.Erasure.DistributionAlgo = formatErasureVersionV2DistributionAlgoV1 278 formats := make([]*formatErasureV3, 32) 279 280 for i := 0; i < setCount; i++ { 281 for j := 0; j < setDriveCount; j++ { 282 newFormat := format.Clone() 283 newFormat.Erasure.This = format.Erasure.Sets[i][j] 284 formats[i*setDriveCount+j] = newFormat 285 } 286 } 287 288 // Return a format from list of formats in quorum. 289 quorumFormat, err := getFormatErasureInQuorum(formats) 290 if err != nil { 291 t.Fatal(err) 292 } 293 294 // Check if the reference format and input formats are same. 295 if err = formatErasureV3Check(quorumFormat, formats[0]); err != nil { 296 t.Fatal(err) 297 } 298 299 // QuorumFormat has .This field empty on purpose, expect a failure. 300 if err = formatErasureV3Check(formats[0], quorumFormat); err == nil { 301 t.Fatal("Unexpected success") 302 } 303 304 formats[0] = nil 305 quorumFormat, err = getFormatErasureInQuorum(formats) 306 if err != nil { 307 t.Fatal(err) 308 } 309 310 badFormat := *quorumFormat 311 badFormat.Erasure.Sets = nil 312 if err = formatErasureV3Check(quorumFormat, &badFormat); err == nil { 313 t.Fatal("Unexpected success") 314 } 315 316 badFormatUUID := *quorumFormat 317 badFormatUUID.Erasure.Sets[0][0] = "bad-uuid" 318 if err = formatErasureV3Check(quorumFormat, &badFormatUUID); err == nil { 319 t.Fatal("Unexpected success") 320 } 321 322 badFormatSetSize := *quorumFormat 323 badFormatSetSize.Erasure.Sets[0] = nil 324 if err = formatErasureV3Check(quorumFormat, &badFormatSetSize); err == nil { 325 t.Fatal("Unexpected success") 326 } 327 328 for i := range formats { 329 if i < 17 { 330 formats[i] = nil 331 } 332 } 333 if _, err = getFormatErasureInQuorum(formats); err == nil { 334 t.Fatal("Unexpected success") 335 } 336 } 337 338 // Get backend Erasure format in quorum `format.json`. 339 func getFormatErasureInQuorumOld(formats []*formatErasureV3) (*formatErasureV3, error) { 340 formatHashes := make([]string, len(formats)) 341 for i, format := range formats { 342 if format == nil { 343 continue 344 } 345 h := sha256.New() 346 for _, set := range format.Erasure.Sets { 347 for _, diskID := range set { 348 h.Write([]byte(diskID)) 349 } 350 } 351 formatHashes[i] = hex.EncodeToString(h.Sum(nil)) 352 } 353 354 formatCountMap := make(map[string]int) 355 for _, hash := range formatHashes { 356 if hash == "" { 357 continue 358 } 359 formatCountMap[hash]++ 360 } 361 362 maxHash := "" 363 maxCount := 0 364 for hash, count := range formatCountMap { 365 if count > maxCount { 366 maxCount = count 367 maxHash = hash 368 } 369 } 370 371 if maxCount < len(formats)/2 { 372 return nil, errErasureReadQuorum 373 } 374 375 for i, hash := range formatHashes { 376 if hash == maxHash { 377 format := formats[i].Clone() 378 format.Erasure.This = "" 379 return format, nil 380 } 381 } 382 383 return nil, errErasureReadQuorum 384 } 385 386 func BenchmarkGetFormatErasureInQuorumOld(b *testing.B) { 387 setCount := 200 388 setDriveCount := 15 389 390 format := newFormatErasureV3(setCount, setDriveCount) 391 format.Erasure.DistributionAlgo = formatErasureVersionV2DistributionAlgoV1 392 formats := make([]*formatErasureV3, 15*200) 393 394 for i := 0; i < setCount; i++ { 395 for j := 0; j < setDriveCount; j++ { 396 newFormat := format.Clone() 397 newFormat.Erasure.This = format.Erasure.Sets[i][j] 398 formats[i*setDriveCount+j] = newFormat 399 } 400 } 401 402 b.ResetTimer() 403 b.ReportAllocs() 404 405 for i := 0; i < b.N; i++ { 406 _, _ = getFormatErasureInQuorumOld(formats) 407 } 408 } 409 410 func BenchmarkGetFormatErasureInQuorum(b *testing.B) { 411 setCount := 200 412 setDriveCount := 15 413 414 format := newFormatErasureV3(setCount, setDriveCount) 415 format.Erasure.DistributionAlgo = formatErasureVersionV2DistributionAlgoV1 416 formats := make([]*formatErasureV3, 15*200) 417 418 for i := 0; i < setCount; i++ { 419 for j := 0; j < setDriveCount; j++ { 420 newFormat := format.Clone() 421 newFormat.Erasure.This = format.Erasure.Sets[i][j] 422 formats[i*setDriveCount+j] = newFormat 423 } 424 } 425 426 b.ResetTimer() 427 b.ReportAllocs() 428 429 for i := 0; i < b.N; i++ { 430 _, _ = getFormatErasureInQuorum(formats) 431 } 432 } 433 434 // Tests formatErasureGetDeploymentID() 435 func TestGetErasureID(t *testing.T) { 436 setCount := 2 437 setDriveCount := 8 438 439 format := newFormatErasureV3(setCount, setDriveCount) 440 format.Erasure.DistributionAlgo = formatErasureVersionV2DistributionAlgoV1 441 formats := make([]*formatErasureV3, 16) 442 443 for i := 0; i < setCount; i++ { 444 for j := 0; j < setDriveCount; j++ { 445 newFormat := format.Clone() 446 newFormat.Erasure.This = format.Erasure.Sets[i][j] 447 formats[i*setDriveCount+j] = newFormat 448 } 449 } 450 451 // Return a format from list of formats in quorum. 452 quorumFormat, err := getFormatErasureInQuorum(formats) 453 if err != nil { 454 t.Fatal(err) 455 } 456 457 // Check if the reference format and input formats are same. 458 var id string 459 if id, err = formatErasureGetDeploymentID(quorumFormat, formats); err != nil { 460 t.Fatal(err) 461 } 462 463 if id == "" { 464 t.Fatal("ID cannot be empty.") 465 } 466 467 formats[0] = nil 468 if id, err = formatErasureGetDeploymentID(quorumFormat, formats); err != nil { 469 t.Fatal(err) 470 } 471 if id == "" { 472 t.Fatal("ID cannot be empty.") 473 } 474 475 formats[1].Erasure.Sets[0][0] = "bad-uuid" 476 if id, err = formatErasureGetDeploymentID(quorumFormat, formats); err != nil { 477 t.Fatal(err) 478 } 479 480 if id == "" { 481 t.Fatal("ID cannot be empty.") 482 } 483 484 formats[2].ID = "bad-id" 485 if _, err = formatErasureGetDeploymentID(quorumFormat, formats); !errors.Is(err, errCorruptedFormat) { 486 t.Fatalf("Unexpected error %s", err) 487 } 488 } 489 490 // Initialize new format sets. 491 func TestNewFormatSets(t *testing.T) { 492 setCount := 2 493 setDriveCount := 16 494 495 format := newFormatErasureV3(setCount, setDriveCount) 496 format.Erasure.DistributionAlgo = formatErasureVersionV2DistributionAlgoV1 497 formats := make([]*formatErasureV3, 32) 498 errs := make([]error, 32) 499 500 for i := 0; i < setCount; i++ { 501 for j := 0; j < setDriveCount; j++ { 502 newFormat := format.Clone() 503 newFormat.Erasure.This = format.Erasure.Sets[i][j] 504 formats[i*setDriveCount+j] = newFormat 505 } 506 } 507 508 quorumFormat, err := getFormatErasureInQuorum(formats) 509 if err != nil { 510 t.Fatal(err) 511 } 512 513 // 16th disk is unformatted. 514 errs[15] = errUnformattedDisk 515 516 newFormats, _ := newHealFormatSets(quorumFormat, setCount, setDriveCount, formats, errs) 517 if newFormats == nil { 518 t.Fatal("Unexpected failure") 519 } 520 521 // Check if deployment IDs are preserved. 522 for i := range newFormats { 523 for j := range newFormats[i] { 524 if newFormats[i][j] == nil { 525 continue 526 } 527 if newFormats[i][j].ID != quorumFormat.ID { 528 t.Fatal("Deployment id in the new format is lost") 529 } 530 } 531 } 532 } 533 534 func BenchmarkInitStorageDisks256(b *testing.B) { 535 benchmarkInitStorageDisksN(b, 256) 536 } 537 538 func BenchmarkInitStorageDisks1024(b *testing.B) { 539 benchmarkInitStorageDisksN(b, 1024) 540 } 541 542 func BenchmarkInitStorageDisks2048(b *testing.B) { 543 benchmarkInitStorageDisksN(b, 2048) 544 } 545 546 func BenchmarkInitStorageDisksMax(b *testing.B) { 547 benchmarkInitStorageDisksN(b, 32*204) 548 } 549 550 func benchmarkInitStorageDisksN(b *testing.B, nDisks int) { 551 b.ResetTimer() 552 b.ReportAllocs() 553 554 fsDirs, err := getRandomDisks(nDisks) 555 if err != nil { 556 b.Fatal(err) 557 } 558 559 endpoints := mustGetNewEndpoints(0, 16, fsDirs...) 560 b.RunParallel(func(pb *testing.PB) { 561 endpoints := endpoints 562 for pb.Next() { 563 initStorageDisksWithErrors(endpoints, storageOpts{cleanUp: false, healthCheck: false}) 564 } 565 }) 566 }