influxdb/cmd/influxd/upgrade/config.go

207 lines
6.9 KiB
Go

package upgrade
// Configuration file upgrade implementation.
// The strategy is to transform only those entries for which rule exists.
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/BurntSushi/toml"
"go.uber.org/zap"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
// passthroughConfigRules maps v1 config key-names to corresponding v2 config key-names.
// The values of these configs will not be modified during the upgrade process.
var passthroughConfigRules = map[string]string{
"reporting-disabled": "reporting-disabled",
"data.dir": "engine-path",
"data.wal-fsync-delay": "storage-wal-fsync-delay",
"data.validate-keys": "storage-validate-keys",
"data.cache-max-memory-size": "storage-cache-max-memory-size",
"data.cache-snapshot-memory-size": "storage-cache-snapshot-memory-size",
"data.cache-snapshot-write-cold-duration": "storage-cache-snapshot-write-cold-duration",
"data.compact-full-write-cold-duration": "storage-compact-full-write-cold-duration",
"data.compact-throughput-burst": "storage-compact-throughput-burst",
"data.max-concurrent-compactions": "storage-max-concurrent-compactions",
"data.max-index-log-file-size": "storage-max-index-log-file-size",
"data.series-id-set-cache-size": "storage-series-id-set-cache-size",
"data.series-file-max-concurrent-snapshot-compactions": "storage-series-file-max-concurrent-snapshot-compactions",
"data.tsm-use-madv-willneed": "storage-tsm-use-madv-willneed",
"retention.check-interval": "storage-retention-check-interval",
"shard-precreation.check-interval": "storage-shard-precreator-check-interval",
"shard-precreation.advance-period": "storage-shard-precreator-advance-period",
"coordinator.max-concurrent-queries": "query-concurrency",
"coordinator.max-select-point": "influxql-max-select-point",
"coordinator.max-select-series": "influxql-max-select-series",
"coordinator.max-select-buckets": "influxql-max-select-buckets",
"logging.level": "log-level",
"http.bind-address": "http-bind-address",
"http.https-certificate": "tls-cert",
"http.https-private-key": "tls-key",
}
func loadV1Config(configFile string) (*configV1, *map[string]interface{}, error) {
_, err := os.Stat(configFile)
if err != nil {
return nil, nil, fmt.Errorf("1.x config file '%s' does not exist", configFile)
}
// load 1.x config content into byte array
bs, err := load(configFile)
if err != nil {
return nil, nil, err
}
// parse it into simplified v1 config used as return value
var configV1 configV1
_, err = toml.Decode(string(bs), &configV1)
if err != nil {
return nil, nil, err
}
// parse into a generic config map
var cAny map[string]interface{}
_, err = toml.Decode(string(bs), &cAny)
if err != nil {
return nil, nil, err
}
return &configV1, &cAny, nil
}
func load(path string) ([]byte, error) {
bs, err := os.ReadFile(path)
if err != nil {
return nil, err
}
// From master-1.x/cmd/influxd/run/config.go:
// Handle any potential Byte-Order-Marks that may be in the config file.
// This is for Windows compatibility only.
// See https://github.com/influxdata/telegraf/issues/1378 and
// https://github.com/influxdata/influxdb/issues/8965.
bom := unicode.BOMOverride(transform.Nop)
bs, _, err = transform.Bytes(bom, bs)
return bs, err
}
// upgradeConfig upgrades existing 1.x configuration file to 2.x influxdb.toml file.
func upgradeConfig(v1Config map[string]interface{}, targetOptions optionsV2, log *zap.Logger) error {
// create and initialize helper
cu := &configUpgrader{
rules: passthroughConfigRules,
log: log,
}
// rewrite config options from V1 to V2 paths
cTransformed := cu.transform(v1Config)
// update new config with upgrade command options
cu.updateV2Config(cTransformed, targetOptions)
// write the ugpraded config to disk
return cu.save(cTransformed, targetOptions.configPath)
}
// configUpgrader is a helper used by `upgrade-config` command.
type configUpgrader struct {
rules map[string]string
log *zap.Logger
}
func (cu *configUpgrader) updateV2Config(config map[string]interface{}, targetOptions optionsV2) {
if targetOptions.enginePath != "" {
config["engine-path"] = targetOptions.enginePath
}
if targetOptions.boltPath != "" {
config["bolt-path"] = targetOptions.boltPath
}
}
func (cu *configUpgrader) save(config map[string]interface{}, path string) error {
// Open the target file, creating parent directories if needed.
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return err
}
outFile, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer outFile.Close()
// Encode the config directly into the file as TOML.
return toml.NewEncoder(outFile).Encode(&config)
}
// Credits: @rogpeppe (Roger Peppe)
func (cu *configUpgrader) transform(v1Config map[string]interface{}) map[string]interface{} {
res := make(map[string]interface{})
for old, new := range cu.rules {
if val, ok := cu.lookup(v1Config, old); ok {
res[new] = val
}
}
// Special case: flip the value for pprof.
if val, ok := cu.lookup(v1Config, "http.pprof-enabled"); ok {
if b, ok := val.(bool); ok {
res["pprof-disabled"] = !b
}
}
// Special case: ensure query settings are valid.
fixQueryLimits(res)
return res
}
// fixQueryLimits ensures that all query-related config settings are compatible
// with the upgraded value of the 'query-concurrency' setting.
func fixQueryLimits(v2Config map[string]interface{}) {
concurrencyVal, ok := v2Config["query-concurrency"]
if !ok {
return
}
var concurrency int64
switch c := concurrencyVal.(type) {
case int:
concurrency = int64(c)
case int32:
concurrency = int64(c)
case int64:
concurrency = c
default:
concurrency = 0
}
if concurrency == 0 {
// The upgrade process doesn't generate a value for query-queue-size, so if
// query-concurrency is 0 / unset then it's safe to leave query-queue-size unset.
return
}
// When query-concurrency is > 0, query-queue-size must also be > 0.
v2Config["query-queue-size"] = concurrency
}
func (cu *configUpgrader) lookup(v1Config map[string]interface{}, path string) (interface{}, bool) {
for {
elem, rest, _ := strings.Cut(path, ".")
val, ok := v1Config[elem]
if rest == "" {
return val, ok
}
child, ok := val.(map[string]interface{})
if !ok {
return nil, false
}
path, v1Config = rest, child
}
}