Merge pull request #3385 from mheon/read_only_validate

Make configuration validation not require a DB commit
This commit is contained in:
OpenShift Merge Robot
2019-06-20 18:06:43 -07:00
committed by GitHub

View File

@ -72,98 +72,160 @@ var (
volPathKey = []byte(volPathName) volPathKey = []byte(volPathName)
) )
// This represents a field in the runtime configuration that will be validated
// against the DB to ensure no configuration mismatches occur.
type dbConfigValidation struct {
name string // Only used for error messages
runtimeValue string
key []byte
defaultValue string
}
// Check if the configuration of the database is compatible with the // Check if the configuration of the database is compatible with the
// configuration of the runtime opening it // configuration of the runtime opening it
// If there is no runtime configuration loaded, load our own // If there is no runtime configuration loaded, load our own
func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error { func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error {
err := db.Update(func(tx *bolt.Tx) error { storeOpts, err := storage.DefaultStoreOptions(rootless.IsRootless(), rootless.GetRootlessUID())
if err != nil {
return err
}
// We need to validate the following things
checks := []dbConfigValidation{
{
"OS",
runtime.GOOS,
osKey,
runtime.GOOS,
},
{
"libpod root directory (staticdir)",
rt.config.StaticDir,
staticDirKey,
"",
},
{
"libpod temporary files directory (tmpdir)",
rt.config.TmpDir,
tmpDirKey,
"",
},
{
"storage temporary directory (runroot)",
rt.config.StorageConfig.RunRoot,
runRootKey,
storeOpts.RunRoot,
},
{
"storage graph root directory (graphroot)",
rt.config.StorageConfig.GraphRoot,
graphRootKey,
storeOpts.GraphRoot,
},
{
"storage graph driver",
rt.config.StorageConfig.GraphDriverName,
graphDriverKey,
storeOpts.GraphDriverName,
},
{
"volume path",
rt.config.VolumePath,
volPathKey,
"",
},
}
// These fields were missing and will have to be recreated.
missingFields := []dbConfigValidation{}
// Let's try and validate read-only first
err = db.View(func(tx *bolt.Tx) error {
configBkt, err := getRuntimeConfigBucket(tx) configBkt, err := getRuntimeConfigBucket(tx)
if err != nil { if err != nil {
return err return err
} }
if err := validateDBAgainstConfig(configBkt, "OS", runtime.GOOS, osKey, runtime.GOOS); err != nil { for _, check := range checks {
return err exists, err := readOnlyValidateConfig(configBkt, check)
}
if err := validateDBAgainstConfig(configBkt, "libpod root directory (staticdir)",
rt.config.StaticDir, staticDirKey, ""); err != nil {
return err
}
if err := validateDBAgainstConfig(configBkt, "libpod temporary files directory (tmpdir)",
rt.config.TmpDir, tmpDirKey, ""); err != nil {
return err
}
storeOpts, err := storage.DefaultStoreOptions(rootless.IsRootless(), rootless.GetRootlessUID())
if err != nil { if err != nil {
return err return err
} }
if err := validateDBAgainstConfig(configBkt, "storage temporary directory (runroot)", if !exists {
rt.config.StorageConfig.RunRoot, runRootKey, missingFields = append(missingFields, check)
storeOpts.RunRoot); err != nil { }
return err
} }
if err := validateDBAgainstConfig(configBkt, "storage graph root directory (graphroot)", return nil
rt.config.StorageConfig.GraphRoot, graphRootKey,
storeOpts.GraphRoot); err != nil {
return err
}
if err := validateDBAgainstConfig(configBkt, "storage graph driver",
rt.config.StorageConfig.GraphDriverName,
graphDriverKey,
storeOpts.GraphDriverName); err != nil {
return err
}
return validateDBAgainstConfig(configBkt, "volume path",
rt.config.VolumePath, volPathKey, "")
}) })
if err != nil {
return err return err
} }
// Validate a configuration entry in the DB against current runtime config if len(missingFields) == 0 {
// If the given configuration key does not exist it will be created return nil
// If the given runtimeValue or value retrieved from the database are the empty }
// string and defaultValue is not, defaultValue will be checked instead. This
// ensures that we will not fail on configuration changes in configured c/storage. // Populate missing fields
func validateDBAgainstConfig(bucket *bolt.Bucket, fieldName, runtimeValue string, keyName []byte, defaultValue string) error { return db.Update(func(tx *bolt.Tx) error {
keyBytes := bucket.Get(keyName) configBkt, err := getRuntimeConfigBucket(tx)
if err != nil {
return err
}
for _, missing := range missingFields {
dbValue := []byte(missing.runtimeValue)
if missing.runtimeValue == "" && missing.defaultValue != "" {
dbValue = []byte(missing.defaultValue)
}
if err := configBkt.Put(missing.key, dbValue); err != nil {
return errors.Wrapf(err, "error updating %s in DB runtime config", missing.name)
}
}
return nil
})
}
// Attempt a read-only validation of a configuration entry in the DB against an
// element of the current runtime configuration.
// If the configuration key in question does not exist, (false, nil) will be
// returned.
// If the configuration key does exist, and matches the runtime configuration
// successfully, (true, nil) is returned.
// An error is only returned when validation fails.
// if the given runtimeValue or value retrieved from the database are empty,
// and defaultValue is not, defaultValue will be checked instead. This ensures
// that we will not fail on configuration changes in c/storage (where we may
// pass the empty string to use defaults).
func readOnlyValidateConfig(bucket *bolt.Bucket, toCheck dbConfigValidation) (bool, error) {
keyBytes := bucket.Get(toCheck.key)
if keyBytes == nil { if keyBytes == nil {
dbValue := []byte(runtimeValue) // False return indicates missing key
if runtimeValue == "" && defaultValue != "" { return false, nil
dbValue = []byte(defaultValue)
} }
if err := bucket.Put(keyName, dbValue); err != nil { dbValue := string(keyBytes)
return errors.Wrapf(err, "error updating %s in DB runtime config", fieldName)
} if toCheck.runtimeValue != dbValue {
} else { // If the runtime value is the empty string and default is not,
if runtimeValue != string(keyBytes) { // check against default.
// If runtimeValue is the empty string, check against if toCheck.runtimeValue == "" && toCheck.defaultValue != "" && dbValue == toCheck.defaultValue {
// the default return true, nil
if runtimeValue == "" && defaultValue != "" &&
string(keyBytes) == defaultValue {
return nil
} }
// If DB value is the empty string, check that the // If the DB value is the empty string, check that the runtime
// runtime value is the default // value is the default.
if string(keyBytes) == "" && defaultValue != "" && if dbValue == "" && toCheck.defaultValue != "" && toCheck.runtimeValue == toCheck.defaultValue {
runtimeValue == defaultValue { return true, nil
return nil
} }
return errors.Wrapf(ErrDBBadConfig, "database %s %s does not match our %s %s", return true, errors.Wrapf(ErrDBBadConfig, "database %s %q does not match our %s %q",
fieldName, string(keyBytes), fieldName, runtimeValue) toCheck.name, dbValue, toCheck.name, toCheck.runtimeValue)
}
} }
return nil return true, nil
} }
// Open a connection to the database. // Open a connection to the database.