cmd/storagenode: enable migration of configs of different types (#3189)

* move deprecated flags code to deprecated.go, refactor to allow migration of other flags
* hide deprecated flags
This commit is contained in:
Cameron 2019-10-08 14:26:53 -04:00 committed by GitHub
parent 447c219d92
commit 10b364a2da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 217 additions and 106 deletions

View File

@ -0,0 +1,116 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"fmt"
"reflect"
"strconv"
"time"
"go.uber.org/zap"
)
// Deprecated contains deprecated config structs
type Deprecated struct {
Kademlia struct {
ExternalAddress string `default:"" hidden:"true"`
Operator struct {
Email string `default:"" hidden:"true"`
Wallet string `default:"" hidden:"true"`
}
}
}
// maps deprecated config values to new values if applicable
func mapDeprecatedConfigs(log *zap.Logger) {
type migration struct {
newValue interface{}
newConfigString string
oldValue string
oldConfigString string
}
migrations := []migration{
{
newValue: &runCfg.Contact.ExternalAddress,
newConfigString: "contact.external-address",
oldValue: runCfg.Deprecated.Kademlia.ExternalAddress,
oldConfigString: "kademlia.external-address",
},
{
newValue: &runCfg.Operator.Wallet,
newConfigString: "operator.wallet",
oldValue: runCfg.Deprecated.Kademlia.Operator.Wallet,
oldConfigString: "kademlia.operator.wallet",
},
{
newValue: &runCfg.Operator.Email,
newConfigString: "operator.email",
oldValue: runCfg.Deprecated.Kademlia.Operator.Email,
oldConfigString: "kademlia.operator.email",
},
}
for _, migration := range migrations {
if migration.oldValue != "" {
typ := reflect.TypeOf(migration.newValue).Elem()
override := parseOverride(typ, migration.oldValue)
reflect.ValueOf(migration.newValue).Elem().Set(reflect.ValueOf(override))
log.Sugar().Debugf("Found deprecated flag. Migrating value %v from %s to %s", reflect.ValueOf(migration.newValue).Elem(), migration.oldConfigString, migration.newConfigString)
}
}
}
func parseOverride(typ reflect.Type, value string) interface{} {
switch typ {
case reflect.TypeOf(int(0)):
val, err := strconv.ParseInt(value, 0, strconv.IntSize)
if err != nil {
panic(err)
}
return int(val)
case reflect.TypeOf(int64(0)):
val, err := strconv.ParseInt(value, 0, 64)
if err != nil {
panic(err)
}
return val
case reflect.TypeOf(uint(0)):
val, err := strconv.ParseUint(value, 0, strconv.IntSize)
if err != nil {
panic(err)
}
return uint(val)
case reflect.TypeOf(uint64(0)):
val, err := strconv.ParseUint(value, 0, 64)
if err != nil {
panic(err)
}
return val
case reflect.TypeOf(time.Duration(0)):
val, err := time.ParseDuration(value)
if err != nil {
panic(err)
}
return val
case reflect.TypeOf(float64(0)):
val, err := strconv.ParseFloat(value, 64)
if err != nil {
panic(err)
}
return val
case reflect.TypeOf(string("")):
return value
case reflect.TypeOf(bool(false)):
val, err := strconv.ParseBool(value)
if err != nil {
panic(err)
}
return val
default:
panic(fmt.Sprintf("invalid field type: %s", typ.String()))
}
}

View File

@ -35,14 +35,6 @@ type StorageNodeFlags struct {
Deprecated
}
// Deprecated contains deprecated config structs
type Deprecated struct {
Kademlia struct {
ExternalAddress string `user:"true" help:"the public address of the Kademlia node, useful for nodes behind NAT" default:""`
Operator storagenode.OperatorConfig
}
}
var (
rootCmd = &cobra.Command{
Use: "storagenode",
@ -303,48 +295,6 @@ func cmdDiag(cmd *cobra.Command, args []string) (err error) {
return nil
}
// maps deprecated config values to new values if applicable
func mapDeprecatedConfigs(log *zap.Logger) {
type migration struct {
newValue *string
newConfigString string
oldValue *string
oldConfigString string
}
migrations := []migration{
{
newValue: &runCfg.Contact.ExternalAddress,
newConfigString: "contact.external-address",
oldValue: &runCfg.Kademlia.ExternalAddress,
oldConfigString: "kademlia.external-address",
},
{
newValue: &runCfg.Operator.Wallet,
newConfigString: "operator.wallet",
oldValue: &runCfg.Kademlia.Operator.Wallet,
oldConfigString: "kademlia.operator.wallet",
},
{
newValue: &runCfg.Operator.Email,
newConfigString: "operator.email",
oldValue: &runCfg.Kademlia.Operator.Email,
oldConfigString: "kademlia.operator.email",
},
}
for _, migration := range migrations {
if *migration.newValue != "" && *migration.oldValue != "" {
log.Sugar().Debugf("Both %s and %s are designated in your config.yaml. %s is deprecated. Using %s with the value of %v. Please update your config.",
migration.oldConfigString, migration.newConfigString, migration.oldConfigString, migration.newConfigString, *migration.newValue)
}
if *migration.newValue == "" && *migration.oldValue != "" {
*migration.newValue = *migration.oldValue
log.Sugar().Debugf("%s is deprecated. Please update your config file with %s.", migration.oldConfigString, migration.newConfigString)
log.Sugar().Debugf("Setting %s to the value of %s: %v.", migration.newConfigString, migration.oldConfigString, *migration.oldValue)
}
}
}
func main() {
process.Exec(rootCmd)
}

View File

@ -4,81 +4,61 @@
package main
import (
"reflect"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)
func TestMapConfigs(t *testing.T) {
func TestMapDeprecatedConfigs(t *testing.T) {
log := zap.L()
cases := []struct {
testID string
newExternalAddr string
oldExternalAddr string
expectedAddr string
newWallet string
oldWallet string
expectedWallet string
newEmail string
oldEmail string
expectedEmail string
testID string
newExternalAddr string
deprecatedExternalAddr string
expectedAddr string
newWallet string
deprecatedWallet string
expectedWallet string
newEmail string
deprecatedEmail string
expectedEmail string
}{
{testID: "new and old present, use new",
newExternalAddr: "newAddr",
oldExternalAddr: "oldAddr",
expectedAddr: "newAddr",
newWallet: "newWallet",
oldWallet: "oldWallet",
expectedWallet: "newWallet",
newEmail: "newEmail",
oldEmail: "oldEmail",
expectedEmail: "newEmail",
{testID: "deprecated present, override",
newExternalAddr: "newAddr",
deprecatedExternalAddr: "oldAddr",
expectedAddr: "oldAddr",
newWallet: "newWallet",
deprecatedWallet: "oldWallet",
expectedWallet: "oldWallet",
newEmail: "newEmail",
deprecatedEmail: "oldEmail",
expectedEmail: "oldEmail",
},
{testID: "old present, new not, use old",
newExternalAddr: "",
oldExternalAddr: "oldAddr",
expectedAddr: "oldAddr",
newWallet: "",
oldWallet: "oldWallet",
expectedWallet: "oldWallet",
newEmail: "",
oldEmail: "oldEmail",
expectedEmail: "oldEmail",
},
{testID: "new present, old not, use new",
newExternalAddr: "newAddr",
oldExternalAddr: "",
expectedAddr: "newAddr",
newWallet: "newWallet",
oldWallet: "",
expectedWallet: "newWallet",
newEmail: "newEmail",
oldEmail: "",
expectedEmail: "newEmail",
},
{testID: "neither present",
newExternalAddr: "",
oldExternalAddr: "",
expectedAddr: "",
newWallet: "",
oldWallet: "",
expectedWallet: "",
newEmail: "",
oldEmail: "",
expectedEmail: "",
{testID: "deprecated absent, do not override",
newExternalAddr: "newAddr",
deprecatedExternalAddr: "",
expectedAddr: "newAddr",
newWallet: "newWallet",
deprecatedWallet: "",
expectedWallet: "newWallet",
newEmail: "newEmail",
deprecatedEmail: "",
expectedEmail: "newEmail",
},
}
for _, c := range cases {
testCase := c
t.Run(testCase.testID, func(t *testing.T) {
runCfg.Contact.ExternalAddress = testCase.newExternalAddr
runCfg.Kademlia.ExternalAddress = testCase.oldExternalAddr
runCfg.Deprecated.Kademlia.ExternalAddress = testCase.deprecatedExternalAddr
runCfg.Operator.Wallet = testCase.newWallet
runCfg.Kademlia.Operator.Wallet = testCase.oldWallet
runCfg.Deprecated.Kademlia.Operator.Wallet = testCase.deprecatedWallet
runCfg.Operator.Email = testCase.newEmail
runCfg.Kademlia.Operator.Email = testCase.oldEmail
runCfg.Deprecated.Kademlia.Operator.Email = testCase.deprecatedEmail
mapDeprecatedConfigs(log)
require.Equal(t, testCase.expectedAddr, runCfg.Contact.ExternalAddress)
require.Equal(t, testCase.expectedWallet, runCfg.Operator.Wallet)
@ -86,3 +66,68 @@ func TestMapConfigs(t *testing.T) {
})
}
}
func TestParseOverride(t *testing.T) {
cases := []struct {
testID string
newConfigValue interface{}
oldConfigValue string
expectedNewConfig interface{}
}{
{testID: "test new config is untouched if deprecated is undefined",
newConfigValue: "test-string",
oldConfigValue: "",
expectedNewConfig: "test-string",
},
{testID: "test migrate int",
newConfigValue: int(1),
oldConfigValue: "100",
expectedNewConfig: int(100),
},
{testID: "test migrate int64",
newConfigValue: int64(1),
oldConfigValue: "100",
expectedNewConfig: int64(100),
},
{testID: "test migrate uint",
newConfigValue: uint(1),
oldConfigValue: "2",
expectedNewConfig: uint(2),
},
{testID: "test migrate uint64",
newConfigValue: uint64(1),
oldConfigValue: "100",
expectedNewConfig: uint64(100),
},
{testID: "test migrate time.Duration",
newConfigValue: 1 * time.Hour,
oldConfigValue: "100h",
expectedNewConfig: 100 * time.Hour,
},
{testID: "test migrate float64",
newConfigValue: 0.5,
oldConfigValue: "1.5",
expectedNewConfig: 1.5,
},
{testID: "test migrate string",
newConfigValue: "example@example.com",
oldConfigValue: "migrate@migrate.com",
expectedNewConfig: "migrate@migrate.com",
},
{testID: "test migrate bool",
newConfigValue: false,
oldConfigValue: "true",
expectedNewConfig: true,
},
}
for _, c := range cases {
testCase := c
t.Run(testCase.testID, func(t *testing.T) {
if testCase.oldConfigValue != "" {
typ := reflect.TypeOf(testCase.newConfigValue)
testCase.newConfigValue = parseOverride(typ, testCase.oldConfigValue)
}
require.Equal(t, testCase.expectedNewConfig, testCase.newConfigValue)
})
}
}