You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
603 lines
14 KiB
603 lines
14 KiB
/*
|
|
* MinIO Cloud Storage, (C) 2018 MinIO, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
// Test get offline/online uuids.
|
|
func TestGetUUIDs(t *testing.T) {
|
|
fmtV2 := newFormatXLV3(4, 16)
|
|
formats := make([]*formatXLV3, 64)
|
|
|
|
for i := 0; i < 4; i++ {
|
|
for j := 0; j < 16; j++ {
|
|
newFormat := *fmtV2
|
|
newFormat.XL.This = fmtV2.XL.Sets[i][j]
|
|
formats[i*16+j] = &newFormat
|
|
}
|
|
}
|
|
|
|
gotCount := len(getOnlineUUIDs(fmtV2, formats))
|
|
if gotCount != 64 {
|
|
t.Errorf("Expected online count '64', got '%d'", gotCount)
|
|
}
|
|
|
|
for i := 0; i < 4; i++ {
|
|
for j := 0; j < 16; j++ {
|
|
if j < 4 {
|
|
formats[i*16+j] = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
gotCount = len(getOnlineUUIDs(fmtV2, formats))
|
|
if gotCount != 48 {
|
|
t.Errorf("Expected online count '48', got '%d'", gotCount)
|
|
}
|
|
|
|
gotCount = len(getOfflineUUIDs(fmtV2, formats))
|
|
if gotCount != 16 {
|
|
t.Errorf("Expected offline count '16', got '%d'", gotCount)
|
|
}
|
|
|
|
markUUIDsOffline(fmtV2, formats)
|
|
gotCount = 0
|
|
for i := range fmtV2.XL.Sets {
|
|
for j := range fmtV2.XL.Sets[i] {
|
|
if fmtV2.XL.Sets[i][j] == offlineDiskUUID {
|
|
gotCount++
|
|
}
|
|
}
|
|
}
|
|
if gotCount != 16 {
|
|
t.Errorf("Expected offline count '16', got '%d'", gotCount)
|
|
}
|
|
}
|
|
|
|
// tests fixFormatXLV3 - fix format.json on all disks.
|
|
func TestFixFormatV3(t *testing.T) {
|
|
xlDirs, err := getRandomDisks(8)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, xlDir := range xlDirs {
|
|
defer os.RemoveAll(xlDir)
|
|
}
|
|
endpoints := mustGetNewEndpoints(xlDirs...)
|
|
|
|
storageDisks, errs := initStorageDisksWithErrors(endpoints)
|
|
for _, err := range errs {
|
|
if err != nil && err != errDiskNotFound {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
format := newFormatXLV3(1, 8)
|
|
formats := make([]*formatXLV3, 8)
|
|
|
|
for j := 0; j < 8; j++ {
|
|
newFormat := format.Clone()
|
|
newFormat.XL.This = format.XL.Sets[0][j]
|
|
formats[j] = newFormat
|
|
}
|
|
|
|
if err = initFormatXLMetaVolume(storageDisks, formats); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
formats[1] = nil
|
|
expThis := formats[2].XL.This
|
|
formats[2].XL.This = ""
|
|
if err := fixFormatXLV3(storageDisks, endpoints, formats); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
newFormats, errs := loadFormatXLAll(storageDisks)
|
|
for _, err := range errs {
|
|
if err != nil && err != errUnformattedDisk {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
gotThis := newFormats[2].XL.This
|
|
if expThis != gotThis {
|
|
t.Fatalf("expected uuid %s, got %s", expThis, gotThis)
|
|
}
|
|
}
|
|
|
|
// tests formatXLV3ThisEmpty conditions.
|
|
func TestFormatXLEmpty(t *testing.T) {
|
|
format := newFormatXLV3(1, 16)
|
|
formats := make([]*formatXLV3, 16)
|
|
|
|
for j := 0; j < 16; j++ {
|
|
newFormat := format.Clone()
|
|
newFormat.XL.This = format.XL.Sets[0][j]
|
|
formats[j] = newFormat
|
|
}
|
|
|
|
// empty format to indicate disk not found, but this
|
|
// empty should return false.
|
|
formats[0] = nil
|
|
|
|
if ok := formatXLV3ThisEmpty(formats); ok {
|
|
t.Fatalf("expected value false, got %t", ok)
|
|
}
|
|
|
|
formats[2].XL.This = ""
|
|
if ok := formatXLV3ThisEmpty(formats); !ok {
|
|
t.Fatalf("expected value true, got %t", ok)
|
|
}
|
|
}
|
|
|
|
// Tests format xl get version.
|
|
func TestFormatXLGetVersion(t *testing.T) {
|
|
// Get test root.
|
|
rootPath, err := getTestRoot()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(rootPath)
|
|
|
|
v := &formatXLVersionDetect{}
|
|
v.XL.Version = "1"
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = ioutil.WriteFile(pathJoin(rootPath, formatConfigFile), b, os.FileMode(0644)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = formatXLGetVersion("not-found")
|
|
if err == nil {
|
|
t.Fatal("Expected to fail but found success")
|
|
}
|
|
|
|
vstr, err := formatXLGetVersion(pathJoin(rootPath, formatConfigFile))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if vstr != "1" {
|
|
t.Fatalf("Expected version '1', got '%s'", vstr)
|
|
}
|
|
}
|
|
|
|
// Tests format get backend format.
|
|
func TestFormatMetaGetFormatBackendXL(t *testing.T) {
|
|
// Get test root.
|
|
rootPath, err := getTestRoot()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(rootPath)
|
|
|
|
m := &formatMetaV1{
|
|
Format: "fs",
|
|
Version: formatMetaVersionV1,
|
|
}
|
|
|
|
b, err := json.Marshal(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = ioutil.WriteFile(pathJoin(rootPath, formatConfigFile), b, os.FileMode(0644)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = formatMetaGetFormatBackendXL("not-found")
|
|
if err == nil {
|
|
t.Fatal("Expected to fail but found success")
|
|
}
|
|
|
|
format, err := formatMetaGetFormatBackendXL(pathJoin(rootPath, formatConfigFile))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if format != m.Format {
|
|
t.Fatalf("Expected format value %s, got %s", m.Format, format)
|
|
}
|
|
|
|
m = &formatMetaV1{
|
|
Format: "xl",
|
|
Version: "2",
|
|
}
|
|
|
|
b, err = json.Marshal(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = ioutil.WriteFile(pathJoin(rootPath, formatConfigFile), b, os.FileMode(0644)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = formatMetaGetFormatBackendXL(pathJoin(rootPath, formatConfigFile))
|
|
if err == nil {
|
|
t.Fatal("Expected to fail with incompatible meta version")
|
|
}
|
|
}
|
|
|
|
// Tests xl format migration.
|
|
func TestFormatXLMigrate(t *testing.T) {
|
|
// Get test root.
|
|
rootPath, err := getTestRoot()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(rootPath)
|
|
|
|
m := &formatXLV1{}
|
|
m.Format = formatBackendXL
|
|
m.Version = formatMetaVersionV1
|
|
m.XL.Version = formatXLVersionV1
|
|
m.XL.Disk = mustGetUUID()
|
|
m.XL.JBOD = []string{m.XL.Disk, mustGetUUID(), mustGetUUID(), mustGetUUID()}
|
|
|
|
b, err := json.Marshal(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = os.MkdirAll(pathJoin(rootPath, minioMetaBucket), os.FileMode(0755)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = ioutil.WriteFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile), b, os.FileMode(0644)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = formatXLMigrate(rootPath); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
migratedVersion, err := formatXLGetVersion(pathJoin(rootPath, minioMetaBucket, formatConfigFile))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if migratedVersion != formatXLVersionV3 {
|
|
t.Fatalf("expected version: %s, got: %s", formatXLVersionV3, migratedVersion)
|
|
}
|
|
|
|
b, err = ioutil.ReadFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
formatV3 := &formatXLV3{}
|
|
if err = json.Unmarshal(b, formatV3); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if formatV3.XL.This != m.XL.Disk {
|
|
t.Fatalf("expected disk uuid: %s, got: %s", m.XL.Disk, formatV3.XL.This)
|
|
}
|
|
if len(formatV3.XL.Sets) != 1 {
|
|
t.Fatalf("expected single set after migrating from v1 to v3, but found %d", len(formatV3.XL.Sets))
|
|
}
|
|
if !reflect.DeepEqual(formatV3.XL.Sets[0], m.XL.JBOD) {
|
|
t.Fatalf("expected disk uuid: %v, got: %v", m.XL.JBOD, formatV3.XL.Sets[0])
|
|
}
|
|
|
|
m = &formatXLV1{}
|
|
m.Format = "unknown"
|
|
m.Version = formatMetaVersionV1
|
|
m.XL.Version = formatXLVersionV1
|
|
m.XL.Disk = mustGetUUID()
|
|
m.XL.JBOD = []string{m.XL.Disk, mustGetUUID(), mustGetUUID(), mustGetUUID()}
|
|
|
|
b, err = json.Marshal(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = ioutil.WriteFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile), b, os.FileMode(0644)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = formatXLMigrate(rootPath); err == nil {
|
|
t.Fatal("Expected to fail with unexpected backend format")
|
|
}
|
|
|
|
m = &formatXLV1{}
|
|
m.Format = formatBackendXL
|
|
m.Version = formatMetaVersionV1
|
|
m.XL.Version = "30"
|
|
m.XL.Disk = mustGetUUID()
|
|
m.XL.JBOD = []string{m.XL.Disk, mustGetUUID(), mustGetUUID(), mustGetUUID()}
|
|
|
|
b, err = json.Marshal(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = ioutil.WriteFile(pathJoin(rootPath, minioMetaBucket, formatConfigFile), b, os.FileMode(0644)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err = formatXLMigrate(rootPath); err == nil {
|
|
t.Fatal("Expected to fail with unexpected backend format version number")
|
|
}
|
|
}
|
|
|
|
// Tests check format xl value.
|
|
func TestCheckFormatXLValue(t *testing.T) {
|
|
testCases := []struct {
|
|
format *formatXLV3
|
|
success bool
|
|
}{
|
|
// Invalid XL format version "2".
|
|
{
|
|
&formatXLV3{
|
|
formatMetaV1: formatMetaV1{
|
|
Version: "2",
|
|
Format: "XL",
|
|
},
|
|
XL: struct {
|
|
Version string `json:"version"`
|
|
This string `json:"this"`
|
|
Sets [][]string `json:"sets"`
|
|
DistributionAlgo string `json:"distributionAlgo"`
|
|
}{
|
|
Version: "2",
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
// Invalid XL format "Unknown".
|
|
{
|
|
&formatXLV3{
|
|
formatMetaV1: formatMetaV1{
|
|
Version: "1",
|
|
Format: "Unknown",
|
|
},
|
|
XL: struct {
|
|
Version string `json:"version"`
|
|
This string `json:"this"`
|
|
Sets [][]string `json:"sets"`
|
|
DistributionAlgo string `json:"distributionAlgo"`
|
|
}{
|
|
Version: "2",
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
// Invalid XL format version "0".
|
|
{
|
|
&formatXLV3{
|
|
formatMetaV1: formatMetaV1{
|
|
Version: "1",
|
|
Format: "XL",
|
|
},
|
|
XL: struct {
|
|
Version string `json:"version"`
|
|
This string `json:"this"`
|
|
Sets [][]string `json:"sets"`
|
|
DistributionAlgo string `json:"distributionAlgo"`
|
|
}{
|
|
Version: "0",
|
|
},
|
|
},
|
|
false,
|
|
},
|
|
}
|
|
|
|
// Valid all test cases.
|
|
for i, testCase := range testCases {
|
|
if err := checkFormatXLValue(testCase.format); err != nil && testCase.success {
|
|
t.Errorf("Test %d: Expected failure %s", i+1, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests getFormatXLInQuorum()
|
|
func TestGetFormatXLInQuorumCheck(t *testing.T) {
|
|
setCount := 2
|
|
drivesPerSet := 16
|
|
|
|
format := newFormatXLV3(setCount, drivesPerSet)
|
|
formats := make([]*formatXLV3, 32)
|
|
|
|
for i := 0; i < setCount; i++ {
|
|
for j := 0; j < drivesPerSet; j++ {
|
|
newFormat := format.Clone()
|
|
newFormat.XL.This = format.XL.Sets[i][j]
|
|
formats[i*drivesPerSet+j] = newFormat
|
|
}
|
|
}
|
|
|
|
// Return a format from list of formats in quorum.
|
|
quorumFormat, err := getFormatXLInQuorum(formats)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Check if the reference format and input formats are same.
|
|
if err = formatXLV3Check(quorumFormat, formats[0]); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// QuorumFormat has .This field empty on purpose, expect a failure.
|
|
if err = formatXLV3Check(formats[0], quorumFormat); err == nil {
|
|
t.Fatal("Unexpected success")
|
|
}
|
|
|
|
formats[0] = nil
|
|
quorumFormat, err = getFormatXLInQuorum(formats)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
badFormat := *quorumFormat
|
|
badFormat.XL.Sets = nil
|
|
if err = formatXLV3Check(quorumFormat, &badFormat); err == nil {
|
|
t.Fatal("Unexpected success")
|
|
}
|
|
|
|
badFormatUUID := *quorumFormat
|
|
badFormatUUID.XL.Sets[0][0] = "bad-uuid"
|
|
if err = formatXLV3Check(quorumFormat, &badFormatUUID); err == nil {
|
|
t.Fatal("Unexpected success")
|
|
}
|
|
|
|
badFormatSetSize := *quorumFormat
|
|
badFormatSetSize.XL.Sets[0] = nil
|
|
if err = formatXLV3Check(quorumFormat, &badFormatSetSize); err == nil {
|
|
t.Fatal("Unexpected success")
|
|
}
|
|
|
|
for i := range formats {
|
|
if i < 17 {
|
|
formats[i] = nil
|
|
}
|
|
}
|
|
if _, err = getFormatXLInQuorum(formats); err == nil {
|
|
t.Fatal("Unexpected success")
|
|
}
|
|
}
|
|
|
|
// Tests formatXLGetDeploymentID()
|
|
func TestGetXLID(t *testing.T) {
|
|
setCount := 2
|
|
drivesPerSet := 8
|
|
|
|
format := newFormatXLV3(setCount, drivesPerSet)
|
|
formats := make([]*formatXLV3, 16)
|
|
|
|
for i := 0; i < setCount; i++ {
|
|
for j := 0; j < drivesPerSet; j++ {
|
|
newFormat := format.Clone()
|
|
newFormat.XL.This = format.XL.Sets[i][j]
|
|
formats[i*drivesPerSet+j] = newFormat
|
|
}
|
|
}
|
|
|
|
// Return a format from list of formats in quorum.
|
|
quorumFormat, err := getFormatXLInQuorum(formats)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Check if the reference format and input formats are same.
|
|
var id string
|
|
if id, err = formatXLGetDeploymentID(quorumFormat, formats); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if id == "" {
|
|
t.Fatal("ID cannot be empty.")
|
|
}
|
|
|
|
formats[0] = nil
|
|
if id, err = formatXLGetDeploymentID(quorumFormat, formats); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if id == "" {
|
|
t.Fatal("ID cannot be empty.")
|
|
}
|
|
|
|
formats[1].XL.Sets[0][0] = "bad-uuid"
|
|
if id, err = formatXLGetDeploymentID(quorumFormat, formats); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if id == "" {
|
|
t.Fatal("ID cannot be empty.")
|
|
}
|
|
|
|
formats[2].ID = "bad-id"
|
|
if _, err = formatXLGetDeploymentID(quorumFormat, formats); err != errCorruptedFormat {
|
|
t.Fatal("Unexpected Success")
|
|
}
|
|
}
|
|
|
|
// Initialize new format sets.
|
|
func TestNewFormatSets(t *testing.T) {
|
|
setCount := 2
|
|
drivesPerSet := 16
|
|
|
|
format := newFormatXLV3(setCount, drivesPerSet)
|
|
formats := make([]*formatXLV3, 32)
|
|
errs := make([]error, 32)
|
|
|
|
for i := 0; i < setCount; i++ {
|
|
for j := 0; j < drivesPerSet; j++ {
|
|
newFormat := format.Clone()
|
|
newFormat.XL.This = format.XL.Sets[i][j]
|
|
formats[i*drivesPerSet+j] = newFormat
|
|
}
|
|
}
|
|
|
|
quorumFormat, err := getFormatXLInQuorum(formats)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// 16th disk is unformatted.
|
|
errs[15] = errUnformattedDisk
|
|
|
|
newFormats := newHealFormatSets(quorumFormat, setCount, drivesPerSet, formats, errs)
|
|
if newFormats == nil {
|
|
t.Fatal("Unexpected failure")
|
|
}
|
|
|
|
// Check if deployment IDs are preserved.
|
|
for i := range newFormats {
|
|
for j := range newFormats[i] {
|
|
if newFormats[i][j].ID != quorumFormat.ID {
|
|
t.Fatal("Deployment id in the new format is lost")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkInitStorageDisks256(b *testing.B) {
|
|
benchmarkInitStorageDisksN(b, 256)
|
|
}
|
|
|
|
func BenchmarkInitStorageDisks1024(b *testing.B) {
|
|
benchmarkInitStorageDisksN(b, 1024)
|
|
}
|
|
|
|
func BenchmarkInitStorageDisks2048(b *testing.B) {
|
|
benchmarkInitStorageDisksN(b, 2048)
|
|
}
|
|
|
|
func BenchmarkInitStorageDisksMax(b *testing.B) {
|
|
benchmarkInitStorageDisksN(b, 32*204)
|
|
}
|
|
|
|
func benchmarkInitStorageDisksN(b *testing.B, nDisks int) {
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
fsDirs, err := getRandomDisks(nDisks)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
endpoints := mustGetNewEndpoints(fsDirs...)
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
endpoints := endpoints
|
|
for pb.Next() {
|
|
initStorageDisksWithErrors(endpoints)
|
|
}
|
|
})
|
|
}
|
|
|