config: Do not migrate config file if not needed. (#4264)

Also improve the error message returned by `pkg/quick`.

Fixes #4233
master
Harshavardhana 8 years ago committed by GitHub
parent 2df1e2e9a9
commit 610dbe3479
  1. 86
      cmd/config-migrate.go
  2. 40
      cmd/config-migrate_test.go
  3. 2
      cmd/server-main.go
  4. 12
      pkg/quick/encoding.go
  5. 25
      pkg/quick/errorutil.go
  6. 12
      pkg/quick/quick.go
  7. 36
      pkg/quick/quick_test.go

@ -27,77 +27,125 @@ import (
// DO NOT EDIT following message template, please open a github issue to discuss instead. // DO NOT EDIT following message template, please open a github issue to discuss instead.
var configMigrateMSGTemplate = "Configuration file %s migrated from version '%s' to '%s' successfully.\n" var configMigrateMSGTemplate = "Configuration file %s migrated from version '%s' to '%s' successfully.\n"
// Migrates all config versions from "1" to "18".
func migrateConfig() error { func migrateConfig() error {
// Purge all configs with version '1'. // Purge all configs with version '1',
// this is a special case since version '1' used
// to be a filename 'fsUsers.json' not 'config.json'.
if err := purgeV1(); err != nil { if err := purgeV1(); err != nil {
return err return err
} }
// Load only config version information.
version, err := quick.GetVersion(getConfigFile())
if err != nil {
return err
}
// Conditional to migrate only relevant config versions.
// Upon success migration continues to the next version in sequence.
switch version {
case "2":
// Migrate version '2' to '3'. // Migrate version '2' to '3'.
if err := migrateV2ToV3(); err != nil { if err = migrateV2ToV3(); err != nil {
return err return err
} }
fallthrough
case "3":
// Migrate version '3' to '4'. // Migrate version '3' to '4'.
if err := migrateV3ToV4(); err != nil { if err = migrateV3ToV4(); err != nil {
return err return err
} }
fallthrough
case "4":
// Migrate version '4' to '5'. // Migrate version '4' to '5'.
if err := migrateV4ToV5(); err != nil { if err = migrateV4ToV5(); err != nil {
return err return err
} }
fallthrough
case "5":
// Migrate version '5' to '6. // Migrate version '5' to '6.
if err := migrateV5ToV6(); err != nil { if err = migrateV5ToV6(); err != nil {
return err return err
} }
fallthrough
case "6":
// Migrate version '6' to '7'. // Migrate version '6' to '7'.
if err := migrateV6ToV7(); err != nil { if err = migrateV6ToV7(); err != nil {
return err return err
} }
fallthrough
case "7":
// Migrate version '7' to '8'. // Migrate version '7' to '8'.
if err := migrateV7ToV8(); err != nil { if err = migrateV7ToV8(); err != nil {
return err return err
} }
fallthrough
case "8":
// Migrate version '8' to '9'. // Migrate version '8' to '9'.
if err := migrateV8ToV9(); err != nil { if err = migrateV8ToV9(); err != nil {
return err return err
} }
fallthrough
case "9":
// Migrate version '9' to '10'. // Migrate version '9' to '10'.
if err := migrateV9ToV10(); err != nil { if err = migrateV9ToV10(); err != nil {
return err return err
} }
fallthrough
case "10":
// Migrate version '10' to '11'. // Migrate version '10' to '11'.
if err := migrateV10ToV11(); err != nil { if err = migrateV10ToV11(); err != nil {
return err return err
} }
fallthrough
case "11":
// Migrate version '11' to '12'. // Migrate version '11' to '12'.
if err := migrateV11ToV12(); err != nil { if err = migrateV11ToV12(); err != nil {
return err return err
} }
fallthrough
case "12":
// Migrate version '12' to '13'. // Migrate version '12' to '13'.
if err := migrateV12ToV13(); err != nil { if err = migrateV12ToV13(); err != nil {
return err return err
} }
fallthrough
case "13":
// Migrate version '13' to '14'. // Migrate version '13' to '14'.
if err := migrateV13ToV14(); err != nil { if err = migrateV13ToV14(); err != nil {
return err return err
} }
fallthrough
case "14":
// Migrate version '14' to '15'. // Migrate version '14' to '15'.
if err := migrateV14ToV15(); err != nil { if err = migrateV14ToV15(); err != nil {
return err return err
} }
fallthrough
case "15":
// Migrate version '15' to '16'. // Migrate version '15' to '16'.
if err := migrateV15ToV16(); err != nil { if err = migrateV15ToV16(); err != nil {
return err return err
} }
fallthrough
case "16":
// Migrate version '16' to '17'. // Migrate version '16' to '17'.
if err := migrateV16ToV17(); err != nil { if err = migrateV16ToV17(); err != nil {
return err return err
} }
fallthrough
case "17":
// Migrate version '17' to '18'. // Migrate version '17' to '18'.
if err := migrateV17ToV18(); err != nil { if err = migrateV17ToV18(); err != nil {
return err return err
} }
fallthrough
return nil case v18:
// No migration needed. this always points to current version.
err = nil
}
return err
} }
// Version '1' is not supported anymore and deprecated, safe to delete. // Version '1' is not supported anymore and deprecated, safe to delete.

@ -17,6 +17,7 @@
package cmd package cmd
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
@ -153,6 +154,7 @@ func TestServerConfigMigrateV2toV18(t *testing.T) {
if err := ioutil.WriteFile(configPath, []byte(configJSON), 0644); err != nil { if err := ioutil.WriteFile(configPath, []byte(configJSON), 0644); err != nil {
t.Fatal("Unexpected error: ", err) t.Fatal("Unexpected error: ", err)
} }
// Fire a migrateConfig() // Fire a migrateConfig()
if err := migrateConfig(); err != nil { if err := migrateConfig(); err != nil {
t.Fatal("Unexpected error: ", err) t.Fatal("Unexpected error: ", err)
@ -191,7 +193,7 @@ func TestServerConfigMigrateFaultyConfig(t *testing.T) {
configPath := rootPath + "/" + minioConfigFile configPath := rootPath + "/" + minioConfigFile
// Create a corrupted config file // Create a corrupted config file
if err := ioutil.WriteFile(configPath, []byte("{ \"version\":\""), 0644); err != nil { if err := ioutil.WriteFile(configPath, []byte("{ \"version\":\"2\", \"test\":"), 0644); err != nil {
t.Fatal("Unexpected error: ", err) t.Fatal("Unexpected error: ", err)
} }
@ -245,3 +247,39 @@ func TestServerConfigMigrateFaultyConfig(t *testing.T) {
t.Fatal("migrateConfigV17ToV18() should fail with a corrupted json") t.Fatal("migrateConfigV17ToV18() should fail with a corrupted json")
} }
} }
// Test if all migrate code returns error with corrupted config files
func TestServerConfigMigrateCorruptedConfig(t *testing.T) {
rootPath, err := newTestConfig(globalMinioDefaultRegion)
if err != nil {
t.Fatalf("Init Test config failed")
}
// remove the root directory after the test ends.
defer removeAll(rootPath)
setConfigDir(rootPath)
configPath := rootPath + "/" + minioConfigFile
for i := 3; i <= 17; i++ {
// Create a corrupted config file
if err = ioutil.WriteFile(configPath, []byte(fmt.Sprintf("{ \"version\":\"%d\", \"credential\": { \"accessKey\": 1 } }", i)),
0644); err != nil {
t.Fatal("Unexpected error: ", err)
}
// Test different migrate versions and be sure they are returning an error
if err = migrateConfig(); err == nil {
t.Fatal("migrateConfig() should fail with a corrupted json")
}
}
// Create a corrupted config file for version '2'.
if err = ioutil.WriteFile(configPath, []byte("{ \"version\":\"2\", \"credentials\": { \"accessKeyId\": 1 } }"), 0644); err != nil {
t.Fatal("Unexpected error: ", err)
}
// Test different migrate versions and be sure they are returning an error
if err = migrateConfig(); err == nil {
t.Fatal("migrateConfig() should fail with a corrupted json")
}
}

@ -107,7 +107,7 @@ func initConfig() {
// Config file does not exist, we create it fresh and return upon success. // Config file does not exist, we create it fresh and return upon success.
if isFile(getConfigFile()) { if isFile(getConfigFile()) {
fatalIf(migrateConfig(), "Config migration failed.") fatalIf(migrateConfig(), "Config migration failed.")
fatalIf(loadConfig(), "Unable to load minio config file") fatalIf(loadConfig(), "Unable to load config version: '%s'.", v18)
} else { } else {
fatalIf(newConfig(), "Unable to initialize minio config for the first time.") fatalIf(newConfig(), "Unable to initialize minio config for the first time.")
log.Println("Created minio configuration file successfully at " + getConfigDir()) log.Println("Created minio configuration file successfully at " + getConfigDir())

@ -21,6 +21,7 @@ package quick
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -55,12 +56,15 @@ func (j jsonEncoding) Unmarshal(b []byte, v interface{}) error {
err := json.Unmarshal(b, v) err := json.Unmarshal(b, v)
if err != nil { if err != nil {
// Try to return a sophisticated json error message if possible // Try to return a sophisticated json error message if possible
switch err := err.(type) { switch jerr := err.(type) {
case *json.SyntaxError: case *json.SyntaxError:
return FormatJSONSyntaxError(bytes.NewReader(b), err) return fmt.Errorf("Unable to parse JSON schema due to a syntax error at '%s'",
default: FormatJSONSyntaxError(bytes.NewReader(b), jerr.Offset))
return err case *json.UnmarshalTypeError:
return fmt.Errorf("Unable to parse JSON, type '%v' cannot be converted into the Go '%v' type",
jerr.Value, jerr.Type)
} }
return err
} }
return nil return nil
} }

@ -21,28 +21,22 @@ package quick
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"github.com/cheggaaa/pb" "github.com/cheggaaa/pb"
) )
const errorFmt = "%5d: %s <-- " const errorFmt = "%5d: %s <<<<"
// FormatJSONSyntaxError generates a pretty printed json syntax error since // FormatJSONSyntaxError generates a pretty printed json syntax error since
// golang doesn't provide an easy way to report the location of the error // golang doesn't provide an easy way to report the location of the error
func FormatJSONSyntaxError(data io.Reader, sErr *json.SyntaxError) error { func FormatJSONSyntaxError(data io.Reader, offset int64) (highlight string) {
if sErr == nil {
return nil
}
var readLine bytes.Buffer var readLine bytes.Buffer
var errLine = 1
var readBytes int64
bio := bufio.NewReader(data) bio := bufio.NewReader(data)
errLine := int64(1)
readBytes := int64(0)
// termWidth is set to a default one to use when we are // termWidth is set to a default one to use when we are
// not able to calculate terminal width via OS syscalls // not able to calculate terminal width via OS syscalls
@ -60,13 +54,10 @@ func FormatJSONSyntaxError(data io.Reader, sErr *json.SyntaxError) error {
for { for {
b, err := bio.ReadByte() b, err := bio.ReadByte()
if err != nil { if err != nil {
if err != io.EOF {
return err
}
break break
} }
readBytes++ readBytes++
if readBytes > sErr.Offset { if readBytes > offset {
break break
} }
switch b { switch b {
@ -88,9 +79,5 @@ func FormatJSONSyntaxError(data io.Reader, sErr *json.SyntaxError) error {
idx = 0 idx = 0
} }
errorStr := fmt.Sprintf("JSON syntax error at line %d, col %d : %s.\n", return fmt.Sprintf(errorFmt, errLine, readLine.String()[idx:])
errLine, readLine.Len(), sErr)
errorStr += fmt.Sprintf(errorFmt, errLine, readLine.String()[idx:])
return errors.New(errorStr)
} }

@ -196,12 +196,22 @@ func New(data interface{}) (Config, error) {
return d, nil return d, nil
} }
// GetVersion - extracts the version information.
func GetVersion(filename string) (version string, err error) {
var qc Config
if qc, err = Load(filename, &struct {
Version string
}{}); err != nil {
return "", err
}
return qc.Version(), err
}
// Load - loads json config from filename for the a given struct data // Load - loads json config from filename for the a given struct data
func Load(filename string, data interface{}) (qc Config, err error) { func Load(filename string, data interface{}) (qc Config, err error) {
if qc, err = New(data); err == nil { if qc, err = New(data); err == nil {
err = qc.Load(filename) err = qc.Load(filename)
} }
return qc, err return qc, err
} }

@ -36,6 +36,42 @@ type MySuite struct{}
var _ = Suite(&MySuite{}) var _ = Suite(&MySuite{})
func (s *MySuite) TestReadVersion(c *C) {
type myStruct struct {
Version string
}
saveMe := myStruct{"1"}
config, err := New(&saveMe)
c.Assert(err, IsNil)
err = config.Save("test.json")
c.Assert(err, IsNil)
version, err := GetVersion("test.json")
c.Assert(err, IsNil)
c.Assert(version, Equals, "1")
}
func (s *MySuite) TestReadVersionErr(c *C) {
type myStruct struct {
Version int
}
saveMe := myStruct{1}
_, err := New(&saveMe)
c.Assert(err, Not(IsNil))
err = ioutil.WriteFile("test.json", []byte("{ \"version\":2,"), 0644)
c.Assert(err, IsNil)
_, err = GetVersion("test.json")
c.Assert(err, Not(IsNil))
err = ioutil.WriteFile("test.json", []byte("{ \"version\":2 }"), 0644)
c.Assert(err, IsNil)
_, err = GetVersion("test.json")
c.Assert(err, Not(IsNil))
}
func (s *MySuite) TestSaveFailOnDir(c *C) { func (s *MySuite) TestSaveFailOnDir(c *C) {
defer os.RemoveAll("test.json") defer os.RemoveAll("test.json")
e := os.MkdirAll("test.json", 0644) e := os.MkdirAll("test.json", 0644)

Loading…
Cancel
Save