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  }