diff --git a/config/config.go b/config/config.go index 9f29cc8adff4d..f5112120320d7 100644 --- a/config/config.go +++ b/config/config.go @@ -16,7 +16,9 @@ package config import ( "crypto/tls" "crypto/x509" + "fmt" "io/ioutil" + "strings" "time" "github.com/BurntSushi/toml" @@ -108,6 +110,18 @@ type Security struct { ClusterSSLKey string `toml:"cluster-ssl-key" json:"cluster-ssl-key"` } +// The ErrConfigValidationFailed error is used so that external callers can do a type assertion +// to defer handling of this specific error when someone does not want strict type checking. +// This is needed only because logging hasn't been set up at the time we parse the config file. +// This should all be ripped out once strict config checking is made the default behavior. +type ErrConfigValidationFailed struct { + err string +} + +func (e *ErrConfigValidationFailed) Error() string { + return e.err +} + // ToTLSConfig generates tls's config based on security section of the config. func (s *Security) ToTLSConfig() (*tls.Config, error) { var tlsConfig *tls.Config @@ -373,11 +387,23 @@ func GetGlobalConfig() *Config { // Load loads config options from a toml file. func (c *Config) Load(confFile string) error { - _, err := toml.DecodeFile(confFile, c) + metaData, err := toml.DecodeFile(confFile, c) if c.TokenLimit <= 0 { c.TokenLimit = 1000 } - return errors.Trace(err) + + // If any items in confFile file are not mapped into the Config struct, issue + // an error and stop the server from starting. + undecoded := metaData.Undecoded() + if len(undecoded) > 0 && err == nil { + var undecodedItems []string + for _, item := range undecoded { + undecodedItems = append(undecodedItems, item.String()) + } + err = &ErrConfigValidationFailed{fmt.Sprintf("config file %s contained unknown configuration options: %s", confFile, strings.Join(undecodedItems, ", "))} + } + + return err } // ToLogConfig converts *Log to *logutil.LogConfig. diff --git a/config/config_test.go b/config/config_test.go index b0d9eb36b3449..06110c2da9cc6 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -45,13 +45,27 @@ func (s *testConfigSuite) TestConfig(c *C) { f, err := os.Create(configFile) c.Assert(err, IsNil) - _, err = f.WriteString(`[performance] + + // Make sure the server refuses to start if there's an unrecognized configuration option + _, err = f.WriteString(` +unrecognized-option-test = true +`) + c.Assert(err, IsNil) + c.Assert(f.Sync(), IsNil) + + c.Assert(conf.Load(configFile), ErrorMatches, "(?:.|\n)*unknown configuration option(?:.|\n)*") + + f.Truncate(0) + f.Seek(0, 0) + + _, err = f.WriteString(` +token-limit = 0 +[performance] [tikv-client] commit-timeout="41s" max-batch-size=128 - -token-limit = -1 `) + c.Assert(err, IsNil) c.Assert(f.Sync(), IsNil) diff --git a/tidb-server/main.go b/tidb-server/main.go index ad3fee1fbbeae..cf295c4565543 100644 --- a/tidb-server/main.go +++ b/tidb-server/main.go @@ -63,6 +63,8 @@ import ( const ( nmVersion = "V" nmConfig = "config" + nmConfigCheck = "config-check" + nmConfigStrict = "config-strict" nmStore = "store" nmStorePath = "path" nmHost = "host" @@ -90,8 +92,10 @@ const ( ) var ( - version = flagBoolean(nmVersion, false, "print version information and exit") - configPath = flag.String(nmConfig, "", "config file path") + version = flagBoolean(nmVersion, false, "print version information and exit") + configPath = flag.String(nmConfig, "", "config file path") + configCheck = flagBoolean(nmConfigCheck, false, "check config file validity and exit") + configStrict = flagBoolean(nmConfigStrict, false, "enforce config file validity") // Base store = flag.String(nmStore, "mocktikv", "registered store name, [tikv, mocktikv]") @@ -141,11 +145,21 @@ func main() { } registerStores() registerMetrics() - loadConfig() + configWarning := loadConfig() overrideConfig() validateConfig() + if *configCheck { + fmt.Println("config check successful") + os.Exit(0) + } setGlobalVars() setupLog() + // If configStrict had been specified, and there had been an error, the server would already + // have exited by now. If configWarning is not an empty string, write it to the log now that + // it's been properly set up. + if configWarning != "" { + log.Warn(configWarning) + } setupTracing() // Should before createServer and after setup config. printInfo() setupBinlogClient() @@ -287,12 +301,20 @@ func flagBoolean(name string, defaultVal bool, usage string) *bool { return flag.Bool(name, defaultVal, usage) } -func loadConfig() { +func loadConfig() string { cfg = config.GetGlobalConfig() if *configPath != "" { err := cfg.Load(*configPath) + // This block is to accommodate an interim situation where strict config checking + // is not the default behavior of TiDB. The warning message must be deferred until + // logging has been set up. After strict config checking is the default behavior, + // This should all be removed. + if _, ok := err.(*config.ErrConfigValidationFailed); ok && !*configCheck && !*configStrict { + return err.Error() + } terror.MustNil(err) } + return "" } func overrideConfig() {