github.com/go-graphite/carbonapi@v0.17.0/cmd/carbonapi/config/init.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"expvar"
     6  	"fmt"
     7  	"os"
     8  	"runtime"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  	"unicode"
    14  
    15  	"github.com/go-graphite/carbonapi/util/pidfile"
    16  	zipperConfig "github.com/go-graphite/carbonapi/zipper/config"
    17  
    18  	"github.com/ansel1/merry"
    19  	"github.com/lomik/zapwriter"
    20  	"github.com/spf13/viper"
    21  	"go.uber.org/zap"
    22  
    23  	"github.com/go-graphite/carbonapi/cache"
    24  	"github.com/go-graphite/carbonapi/expr/functions"
    25  	"github.com/go-graphite/carbonapi/expr/functions/cairo/png"
    26  	fconfig "github.com/go-graphite/carbonapi/expr/functions/config"
    27  	"github.com/go-graphite/carbonapi/expr/helper"
    28  	"github.com/go-graphite/carbonapi/expr/rewrite"
    29  	tconfig "github.com/go-graphite/carbonapi/expr/types/config"
    30  	"github.com/go-graphite/carbonapi/limiter"
    31  	"github.com/go-graphite/carbonapi/pkg/parser"
    32  	zipperTypes "github.com/go-graphite/carbonapi/zipper/types"
    33  )
    34  
    35  var graphTemplates map[string]png.PictureParams
    36  
    37  func truncateTimeSlice(m map[time.Duration]time.Duration) ([]DurationTruncate, error) {
    38  	s := make([]DurationTruncate, len(m))
    39  	n := 0
    40  	for k, v := range m {
    41  		if v <= 0 || k < 0 {
    42  			return nil, fmt.Errorf("invalid duration truncate: %v:%v", k, v)
    43  		}
    44  		s[n] = DurationTruncate{Duration: k, Truncate: v}
    45  		n++
    46  	}
    47  
    48  	s = s[0:n]
    49  	// sort in reverse order
    50  	sort.Slice(s, func(i, j int) bool {
    51  		return s[i].Duration > s[j].Duration
    52  	})
    53  
    54  	return s, nil
    55  }
    56  
    57  func SetUpConfig(logger *zap.Logger, BuildVersion string) {
    58  	Config.ResponseCacheConfig.MemcachedServers = viper.GetStringSlice("cache.memcachedServers")
    59  	Config.BackendCacheConfig.MemcachedServers = viper.GetStringSlice("backendCache.memcachedServers")
    60  	if n := viper.GetString("logger.logger"); n != "" {
    61  		Config.Logger[0].Logger = n
    62  	}
    63  	if n := viper.GetString("logger.file"); n != "" {
    64  		Config.Logger[0].File = n
    65  	}
    66  	if n := viper.GetString("logger.level"); n != "" {
    67  		Config.Logger[0].Level = n
    68  	}
    69  	if n := viper.GetString("logger.encoding"); n != "" {
    70  		Config.Logger[0].Encoding = n
    71  	}
    72  	if n := viper.GetString("logger.encodingtime"); n != "" {
    73  		Config.Logger[0].EncodingTime = n
    74  	}
    75  	if n := viper.GetString("logger.encodingduration"); n != "" {
    76  		Config.Logger[0].EncodingDuration = n
    77  	}
    78  	err := zapwriter.ApplyConfig(Config.Logger)
    79  	if err != nil {
    80  		logger.Fatal("failed to initialize logger with requested configuration",
    81  			zap.Any("configuration", Config.Logger),
    82  			zap.Error(err),
    83  		)
    84  	}
    85  
    86  	needStackTrace := false
    87  	for _, l := range Config.Logger {
    88  		if strings.ToLower(l.Level) == "debug" {
    89  			needStackTrace = true
    90  			break
    91  		}
    92  	}
    93  	merry.SetStackCaptureEnabled(needStackTrace)
    94  
    95  	if Config.GraphTemplates != "" {
    96  		graphTemplates = make(map[string]png.PictureParams)
    97  		graphTemplatesViper := viper.New()
    98  		b, err := os.ReadFile(Config.GraphTemplates)
    99  		if err != nil {
   100  			logger.Fatal("error reading graphTemplates file",
   101  				zap.String("graphTemplate_path", Config.GraphTemplates),
   102  				zap.Error(err),
   103  			)
   104  		}
   105  
   106  		if strings.HasSuffix(Config.GraphTemplates, ".toml") {
   107  			logger.Info("will parse config as toml",
   108  				zap.String("graphTemplate_path", Config.GraphTemplates),
   109  			)
   110  			graphTemplatesViper.SetConfigType("TOML")
   111  		} else {
   112  			logger.Info("will parse config as yaml",
   113  				zap.String("graphTemplate_path", Config.GraphTemplates),
   114  			)
   115  			graphTemplatesViper.SetConfigType("YAML")
   116  		}
   117  
   118  		err = graphTemplatesViper.ReadConfig(bytes.NewBuffer(b))
   119  		if err != nil {
   120  			logger.Fatal("failed to parse config",
   121  				zap.String("graphTemplate_path", Config.GraphTemplates),
   122  				zap.Error(err),
   123  			)
   124  		}
   125  
   126  		for k := range graphTemplatesViper.AllSettings() {
   127  			// we need to explicitly copy	YDivisors and ColorList
   128  			newStruct := png.DefaultParams
   129  			newStruct.ColorList = nil
   130  			newStruct.YDivisors = nil
   131  			sub := graphTemplatesViper.Sub(k)
   132  			err = sub.Unmarshal(&newStruct)
   133  			if err != nil {
   134  				logger.Error("failed to parse graphTemplates config, settings will be ignored",
   135  					zap.String("graphTemplate_path", Config.GraphTemplates),
   136  					zap.Error(err),
   137  				)
   138  			}
   139  			if newStruct.ColorList == nil || len(newStruct.ColorList) == 0 {
   140  				newStruct.ColorList = make([]string, len(png.DefaultParams.ColorList))
   141  				copy(newStruct.ColorList, png.DefaultParams.ColorList)
   142  			}
   143  			if newStruct.YDivisors == nil || len(newStruct.YDivisors) == 0 {
   144  				newStruct.YDivisors = make([]float64, len(png.DefaultParams.YDivisors))
   145  				copy(newStruct.YDivisors, png.DefaultParams.YDivisors)
   146  			}
   147  			graphTemplates[k] = newStruct
   148  		}
   149  
   150  		// skipcq: CRT-P0006
   151  		for name, params := range graphTemplates {
   152  			png.SetTemplate(name, &params)
   153  		}
   154  	}
   155  
   156  	if Config.DefaultColors != nil {
   157  		for name, color := range Config.DefaultColors {
   158  			err = png.SetColor(name, color)
   159  			if err != nil {
   160  				logger.Warn("invalid color specified and will be ignored",
   161  					zap.String("reason", "color must be valid hex rgb or rbga value, e.x. '#c80032', 'c80032', 'c80032ff', etc."),
   162  					zap.Error(err),
   163  				)
   164  			}
   165  		}
   166  	}
   167  
   168  	if Config.FunctionsConfigs != nil {
   169  		logger.Info("extra configuration for functions found",
   170  			zap.Any("extra_config", Config.FunctionsConfigs),
   171  		)
   172  	} else {
   173  		Config.FunctionsConfigs = make(map[string]string)
   174  	}
   175  
   176  	rewrite.New(Config.FunctionsConfigs)
   177  	functions.New(Config.FunctionsConfigs)
   178  
   179  	expvar.NewString("GoVersion").Set(runtime.Version())
   180  	expvar.NewString("BuildVersion").Set(BuildVersion)
   181  	expvar.Publish("config", Config)
   182  
   183  	Config.Limiter = limiter.NewSimpleLimiter(Config.Concurency)
   184  
   185  	Config.ResponseCache = createCache(logger, "cache", &Config.ResponseCacheConfig)
   186  	Config.BackendCache = createCache(logger, "backendCache", &Config.BackendCacheConfig)
   187  
   188  	if Config.TimezoneString != "" {
   189  		fields := strings.Split(Config.TimezoneString, ",")
   190  
   191  		if len(fields) != 2 {
   192  			logger.Fatal("unexpected amount of fields in tz",
   193  				zap.String("timezone_string", Config.TimezoneString),
   194  				zap.Int("fields_got", len(fields)),
   195  				zap.Int("fields_expected", 2),
   196  			)
   197  		}
   198  
   199  		offs, err := strconv.Atoi(fields[1])
   200  		if err != nil {
   201  			logger.Fatal("unable to parse seconds",
   202  				zap.String("field[1]", fields[1]),
   203  				zap.Error(err),
   204  			)
   205  		}
   206  
   207  		Config.DefaultTimeZone = time.FixedZone(fields[0], offs)
   208  		logger.Info("using fixed timezone",
   209  			zap.String("timezone", Config.DefaultTimeZone.String()),
   210  			zap.Int("offset", offs),
   211  		)
   212  	}
   213  
   214  	if len(Config.UnicodeRangeTables) != 0 {
   215  		if strings.ToLower(Config.UnicodeRangeTables[0]) == "all" {
   216  			for _, t := range unicode.Scripts {
   217  				parser.RangeTables = append(parser.RangeTables, t)
   218  			}
   219  		} else {
   220  			for _, stringRange := range Config.UnicodeRangeTables {
   221  				t, ok := unicode.Scripts[stringRange]
   222  				if !ok {
   223  					supportedTables := make([]string, 0)
   224  					for tt := range unicode.Scripts {
   225  						supportedTables = append(supportedTables, tt)
   226  					}
   227  					logger.Fatal("unknown unicode table",
   228  						zap.String("specified_table", stringRange),
   229  						zap.Strings("supported_tables", supportedTables),
   230  						zap.String("more_info", "you need to specify the table, by it's alias in unicode"+
   231  							" 10.0.0, see https://golang.org/src/unicode/tables.go?#L3437"),
   232  					)
   233  				}
   234  				parser.RangeTables = append(parser.RangeTables, t)
   235  			}
   236  		}
   237  	} else {
   238  		parser.RangeTables = append(parser.RangeTables, unicode.Latin)
   239  	}
   240  
   241  	if Config.Cpus != 0 {
   242  		runtime.GOMAXPROCS(Config.Cpus)
   243  	}
   244  
   245  	if Config.PidFile != "" {
   246  		err := pidfile.WritePidFile(Config.PidFile)
   247  		if err != nil {
   248  			logger.Fatal("error when writing pidfile",
   249  				zap.Error(err),
   250  			)
   251  		}
   252  	}
   253  
   254  	helper.ExtrapolatePoints = Config.ExtrapolateExperiment
   255  	if Config.ExtrapolateExperiment {
   256  		logger.Warn("extraploation experiment is enabled",
   257  			zap.String("reason", "this feature is highly experimental and untested"),
   258  		)
   259  	}
   260  
   261  	tconfig.Config.NudgeStartTimeOnAggregation = Config.NudgeStartTimeOnAggregation
   262  	tconfig.Config.UseBucketsHighestTimestampOnAggregation = Config.UseBucketsHighestTimestampOnAggregation
   263  
   264  	if Config.Listen != "" {
   265  		listeners := make(map[string]struct{})
   266  		for _, l := range Config.Listeners {
   267  			listeners[l.Address] = struct{}{}
   268  		}
   269  		if _, ok := listeners[Config.Listen]; !ok {
   270  			Config.Listeners = append(Config.Listeners, Listener{
   271  				Address: Config.Listen,
   272  			})
   273  		}
   274  	}
   275  
   276  	if len(Config.Listeners) == 0 {
   277  		Config.Listeners = append(Config.Listeners, Listener{Address: "127.0.0.1:8081"})
   278  	}
   279  
   280  	for _, define := range Config.Define {
   281  		if define.Name == "" {
   282  			logger.Fatal("empty define name")
   283  		}
   284  		err := parser.Define(define.Name, define.Template)
   285  		if err != nil {
   286  			logger.Fatal("unable to compile define template",
   287  				zap.Error(err),
   288  				zap.String("template", define.Template),
   289  			)
   290  		}
   291  	}
   292  }
   293  
   294  func createCache(logger *zap.Logger, cacheName string, cacheConfig *CacheConfig) cache.BytesCache {
   295  	if cacheConfig.DefaultTimeoutSec <= 0 && cacheConfig.ShortTimeoutSec <= 0 {
   296  		return cache.NullCache{}
   297  	}
   298  	if cacheConfig.ShortTimeoutSec < 0 || cacheConfig.DefaultTimeoutSec == cacheConfig.ShortTimeoutSec {
   299  		// broken value or short timeout not need due to equal
   300  		cacheConfig.ShortTimeoutSec = 0
   301  	}
   302  	if cacheConfig.DefaultTimeoutSec < cacheConfig.ShortTimeoutSec {
   303  		cacheConfig.DefaultTimeoutSec = cacheConfig.ShortTimeoutSec
   304  	}
   305  	if cacheConfig.ShortDuration == 0 {
   306  		cacheConfig.ShortDuration = 3 * time.Hour
   307  	}
   308  	if cacheConfig.ShortUntilOffsetSec == 0 {
   309  		cacheConfig.ShortUntilOffsetSec = 120
   310  	}
   311  
   312  	switch cacheConfig.Type {
   313  	case "memcache":
   314  		if len(cacheConfig.MemcachedServers) == 0 {
   315  			logger.Fatal(cacheName + ": memcache cache requested but no memcache servers provided")
   316  		}
   317  
   318  		logger.Info(cacheName+": memcached configured",
   319  			zap.Strings("servers", cacheConfig.MemcachedServers),
   320  		)
   321  		return cache.NewMemcached("capi-"+cacheName, cacheConfig.MemcachedServers...)
   322  	case "mem":
   323  		logger.Info(cacheName + ": in-memory cache configured")
   324  		return cache.NewExpireCache(uint64(cacheConfig.Size * 1024 * 1024))
   325  	case "null":
   326  		// defaults
   327  		return cache.NullCache{}
   328  	default:
   329  		logger.Error(cacheName+": unknown cache type",
   330  			zap.String("cache_type", cacheConfig.Type),
   331  			zap.Strings("known_cache_types", []string{"null", "mem", "memcache"}),
   332  		)
   333  		return nil
   334  	}
   335  }
   336  
   337  func SetUpViper(logger *zap.Logger, configPath *string, exactConfig bool, viperPrefix string) {
   338  	if *configPath != "" {
   339  		b, err := os.ReadFile(*configPath)
   340  		if err != nil {
   341  			logger.Fatal("error reading config file",
   342  				zap.String("config_path", *configPath),
   343  				zap.Error(err),
   344  			)
   345  		}
   346  
   347  		if strings.HasSuffix(*configPath, ".toml") {
   348  			logger.Info("will parse config as toml",
   349  				zap.String("config_file", *configPath),
   350  			)
   351  			viper.SetConfigType("TOML")
   352  		} else {
   353  			logger.Info("will parse config as yaml",
   354  				zap.String("config_file", *configPath),
   355  			)
   356  			viper.SetConfigType("YAML")
   357  		}
   358  		err = viper.ReadConfig(bytes.NewBuffer(b))
   359  		if err != nil {
   360  			logger.Fatal("failed to parse config",
   361  				zap.String("config_path", *configPath),
   362  				zap.Error(err),
   363  			)
   364  		}
   365  	}
   366  
   367  	if viperPrefix != "" {
   368  		viper.SetEnvPrefix(viperPrefix)
   369  	}
   370  	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
   371  	_ = viper.BindEnv("tz", "carbonapi_tz")
   372  	viper.SetDefault("listeners", []Listener{})
   373  	viper.SetDefault("concurency", 20)
   374  	viper.SetDefault("cache.type", "mem")
   375  	viper.SetDefault("cache.size_mb", 0)
   376  	viper.SetDefault("cache.defaultTimeoutSec", 60)
   377  	viper.SetDefault("cache.memcachedServers", []string{})
   378  	viper.SetDefault("cpus", 0)
   379  	viper.SetDefault("tz", "")
   380  	viper.SetDefault("sendGlobsAsIs", nil)
   381  	viper.SetDefault("alwaysSendGlobsAsIs", nil)
   382  	viper.SetDefault("extractTagsFromArgs", false)
   383  	viper.SetDefault("maxBatchSize", 100)
   384  	viper.SetDefault("graphite.host", "")
   385  	viper.SetDefault("graphite.interval", "60s")
   386  	viper.SetDefault("graphite.prefix", "carbon.api")
   387  	viper.SetDefault("graphite.pattern", "{prefix}.{fqdn}")
   388  	viper.SetDefault("idleConnections", 10)
   389  	viper.SetDefault("pidFile", "")
   390  	viper.SetDefault("upstreams.internalRoutingCache", "600s")
   391  	viper.SetDefault("upstreams.buckets", 10)
   392  	viper.SetDefault("upstreams.sumBuckets", false)
   393  	viper.SetDefault("upstreams.bucketsWidth", []int64{})
   394  	viper.SetDefault("upstreams.bucketsLabels", []string{})
   395  	viper.SetDefault("upstreams.slowLogThreshold", "1s")
   396  	viper.SetDefault("upstreams.timeouts.find", "2s")
   397  	viper.SetDefault("upstreams.timeouts.render", "10s")
   398  	viper.SetDefault("upstreams.timeouts.connect", "200ms")
   399  	viper.SetDefault("upstreams.concurrencyLimitPerServer", 0)
   400  	viper.SetDefault("upstreams.keepAliveInterval", "30s")
   401  	viper.SetDefault("upstreams.maxIdleConnsPerHost", 100)
   402  	viper.SetDefault("upstreams.scaleToCommonStep", true)
   403  	viper.SetDefault("graphite09compat", false)
   404  	viper.SetDefault("expireDelaySec", 600)
   405  	viper.SetDefault("useCachingDNSResolver", false)
   406  	viper.SetDefault("logger", map[string]string{})
   407  	viper.SetDefault("combineMultipleTargetsInOne", false)
   408  	viper.SetDefault("nudgeStartTimeOnAggregation", false)
   409  	viper.SetDefault("useBucketsHighestTimestampOnAggregation", false)
   410  
   411  	viper.AutomaticEnv()
   412  
   413  	var err error
   414  	if exactConfig {
   415  		err = viper.UnmarshalExact(&Config)
   416  	} else {
   417  		err = viper.Unmarshal(&Config)
   418  	}
   419  
   420  	if err != nil {
   421  		logger.Fatal("failed to parse config",
   422  			zap.Error(err),
   423  		)
   424  	}
   425  
   426  	fconfig.Config.ExtractTagsFromArgs = Config.ExtractTagsFromArgs
   427  }
   428  
   429  func SetUpConfigUpstreams(logger *zap.Logger) {
   430  	if Config.Zipper != "" {
   431  		logger.Warn("found legacy 'zipper' option, will use it instead of any 'upstreams' specified. This will be removed in future versions!")
   432  
   433  		Config.Upstreams.Backends = []string{Config.Zipper}
   434  		Config.Upstreams.ConcurrencyLimitPerServer = Config.Concurency
   435  		Config.Upstreams.MaxIdleConnsPerHost = Config.IdleConnections
   436  		Config.Upstreams.MaxBatchSize = &Config.MaxBatchSize
   437  		Config.Upstreams.KeepAliveInterval = 10 * time.Second
   438  		Config.Upstreams.SlowLogThreshold = 1 * time.Second
   439  		// To emulate previous behavior
   440  		Config.Upstreams.Timeouts = zipperTypes.Timeouts{
   441  			Connect: 1 * time.Second,
   442  			Render:  600 * time.Second,
   443  			Find:    600 * time.Second,
   444  		}
   445  		Config.Upstreams.ScaleToCommonStep = true
   446  	}
   447  	if len(Config.Upstreams.Backends) == 0 && len(Config.Upstreams.BackendsV2.Backends) == 0 {
   448  		logger.Fatal("no backends specified for upstreams!")
   449  	}
   450  
   451  	oldStyleGlobsUsed := false
   452  	alwaysSendGlobs := false
   453  	sendGlobs := false
   454  	if Config.AlwaysSendGlobsAsIs != nil {
   455  		alwaysSendGlobs = *Config.AlwaysSendGlobsAsIs
   456  		oldStyleGlobsUsed = true
   457  	}
   458  
   459  	if Config.SendGlobsAsIs != nil {
   460  		alwaysSendGlobs = *Config.SendGlobsAsIs
   461  		oldStyleGlobsUsed = true
   462  	}
   463  
   464  	if oldStyleGlobsUsed {
   465  		if alwaysSendGlobs {
   466  			Config.Upstreams.FallbackMaxBatchSize = 0
   467  		} else if sendGlobs {
   468  			Config.Upstreams.FallbackMaxBatchSize = Config.MaxBatchSize
   469  		} else {
   470  			Config.Upstreams.FallbackMaxBatchSize = 1
   471  		}
   472  	} else {
   473  		Config.Upstreams.FallbackMaxBatchSize = Config.MaxBatchSize
   474  	}
   475  
   476  	Config.Upstreams = *zipperConfig.SanitizeConfig(logger, Config.Upstreams)
   477  
   478  	if Config.Buckets != 10 {
   479  		logger.Warn("`buckets` config option was moved to `upstreams` section, this will be removed in future releases, please migrate your configuration")
   480  		Config.Upstreams.Buckets = Config.Buckets
   481  	}
   482  
   483  	var err error
   484  	Config.TruncateTime, err = truncateTimeSlice(Config.TruncateTimeMap)
   485  	if err != nil {
   486  		logger.Warn("`truncateTime` config option is invalid", zap.Error(err))
   487  	}
   488  }