feat(agent): Introduce CLI option to set config URL retry attempts (#15377)

This commit is contained in:
Joshua Powers 2024-05-21 02:56:52 -06:00 committed by GitHub
parent d8aa46e9a9
commit ad59290166
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 86 additions and 55 deletions

View File

@ -224,6 +224,7 @@ func runApp(args []string, outputBuffer io.Writer, pprof Server, c TelegrafConfi
config: cCtx.StringSlice("config"), config: cCtx.StringSlice("config"),
configDir: cCtx.StringSlice("config-directory"), configDir: cCtx.StringSlice("config-directory"),
testWait: cCtx.Int("test-wait"), testWait: cCtx.Int("test-wait"),
configURLRetryAttempts: cCtx.Int("config-url-retry-attempts"),
watchConfig: cCtx.String("watch-config"), watchConfig: cCtx.String("watch-config"),
pidFile: cCtx.String("pidfile"), pidFile: cCtx.String("pidfile"),
plugindDir: cCtx.String("plugin-directory"), plugindDir: cCtx.String("plugin-directory"),
@ -275,6 +276,11 @@ func runApp(args []string, outputBuffer io.Writer, pprof Server, c TelegrafConfi
Name: "test-wait", Name: "test-wait",
Usage: "wait up to this many seconds for service inputs to complete in test mode", Usage: "wait up to this many seconds for service inputs to complete in test mode",
}, },
&cli.IntFlag{
Name: "config-url-retry-attempts",
Usage: "Number of attempts to obtain a remote configuration via a URL during startup. " +
"Set to -1 for unlimited attempts. (default: 3)",
},
// //
// String flags // String flags
&cli.StringFlag{ &cli.StringFlag{

View File

@ -35,6 +35,7 @@ type GlobalFlags struct {
config []string config []string
configDir []string configDir []string
testWait int testWait int
configURLRetryAttempts int
watchConfig string watchConfig string
pidFile string pidFile string
plugindDir string plugindDir string
@ -248,6 +249,7 @@ func (t *Telegraf) loadConfiguration() (*config.Config, error) {
configFiles = append(configFiles, defaultFiles...) configFiles = append(configFiles, defaultFiles...)
} }
c.Agent.ConfigURLRetryAttempts = t.configURLRetryAttempts
t.configFiles = configFiles t.configFiles = configFiles
if err := c.LoadAll(configFiles...); err != nil { if err := c.LoadAll(configFiles...); err != nil {
return c, err return c, err

View File

@ -271,6 +271,10 @@ type AgentConfig struct {
// By default, processors are run a second time after aggregators. Changing // By default, processors are run a second time after aggregators. Changing
// this setting to true will skip the second run of processors. // this setting to true will skip the second run of processors.
SkipProcessorsAfterAggregators bool `toml:"skip_processors_after_aggregators"` SkipProcessorsAfterAggregators bool `toml:"skip_processors_after_aggregators"`
// Number of attempts to obtain a remote configuration via a URL during
// startup. Set to -1 for unlimited attempts.
ConfigURLRetryAttempts int `toml:"config-url-retry-attempts"`
} }
// InputNames returns a list of strings of the configured inputs. // InputNames returns a list of strings of the configured inputs.
@ -449,7 +453,7 @@ func (c *Config) LoadConfig(path string) error {
log.Printf("I! Loading config: %s", path) log.Printf("I! Loading config: %s", path)
} }
data, _, err := LoadConfigFile(path) data, _, err := LoadConfigFileWithRetries(path, c.Agent.ConfigURLRetryAttempts)
if err != nil { if err != nil {
return fmt.Errorf("error loading config file %s: %w", path, err) return fmt.Errorf("error loading config file %s: %w", path, err)
} }
@ -718,6 +722,10 @@ func trimBOM(f []byte) []byte {
// together with a flag denoting if the file is from a remote location such // together with a flag denoting if the file is from a remote location such
// as a web server. // as a web server.
func LoadConfigFile(config string) ([]byte, bool, error) { func LoadConfigFile(config string) ([]byte, bool, error) {
return LoadConfigFileWithRetries(config, 0)
}
func LoadConfigFileWithRetries(config string, urlRetryAttempts int) ([]byte, bool, error) {
if fetchURLRe.MatchString(config) { if fetchURLRe.MatchString(config) {
u, err := url.Parse(config) u, err := url.Parse(config)
if err != nil { if err != nil {
@ -726,7 +734,7 @@ func LoadConfigFile(config string) ([]byte, bool, error) {
switch u.Scheme { switch u.Scheme {
case "https", "http": case "https", "http":
data, err := fetchConfig(u) data, err := fetchConfig(u, urlRetryAttempts)
return data, true, err return data, true, err
default: default:
return nil, true, fmt.Errorf("scheme %q not supported", u.Scheme) return nil, true, fmt.Errorf("scheme %q not supported", u.Scheme)
@ -747,7 +755,7 @@ func LoadConfigFile(config string) ([]byte, bool, error) {
return buffer, false, nil return buffer, false, nil
} }
func fetchConfig(u *url.URL) ([]byte, error) { func fetchConfig(u *url.URL, urlRetryAttempts int) ([]byte, error) {
req, err := http.NewRequest("GET", u.String(), nil) req, err := http.NewRequest("GET", u.String(), nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -759,38 +767,53 @@ func fetchConfig(u *url.URL) ([]byte, error) {
req.Header.Add("Accept", "application/toml") req.Header.Add("Accept", "application/toml")
req.Header.Set("User-Agent", internal.ProductToken()) req.Header.Set("User-Agent", internal.ProductToken())
retries := 3 var totalAttempts int
for i := 0; i <= retries; i++ { if urlRetryAttempts == -1 {
body, err, retry := func() ([]byte, error, bool) { totalAttempts = -1
resp, err := http.DefaultClient.Do(req) log.Printf("Using unlimited number of attempts to fetch HTTP config")
if err != nil { } else if urlRetryAttempts == 0 {
return nil, fmt.Errorf("retry %d of %d failed connecting to HTTP config server: %w", i, retries, err), false totalAttempts = 3
log.Printf("Using default number of attempts to fetch HTTP config: %d", totalAttempts)
} else if urlRetryAttempts > 0 {
totalAttempts = urlRetryAttempts
} else {
return nil, fmt.Errorf("invalid number of attempts: %d", urlRetryAttempts)
} }
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if i < retries {
log.Printf("Error getting HTTP config. Retry %d of %d in %s. Status=%d", i, retries, httpLoadConfigRetryInterval, resp.StatusCode)
return nil, nil, true
}
return nil, fmt.Errorf("retry %d of %d failed to retrieve remote config: %s", i, retries, resp.Status), false
}
body, err := io.ReadAll(resp.Body)
return body, err, false
}()
if err != nil { attempt := 0
for {
body, err := requestURLConfig(req)
if err == nil {
return body, nil
}
log.Printf("Error getting HTTP config (attempt %d of %d): %s", attempt, totalAttempts, err)
if urlRetryAttempts != -1 && attempt >= totalAttempts {
return nil, err return nil, err
} }
if retry {
time.Sleep(httpLoadConfigRetryInterval) time.Sleep(httpLoadConfigRetryInterval)
continue attempt++
}
}
func requestURLConfig(req *http.Request) ([]byte, error) {
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to connect to HTTP config server: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch HTTP config: %s", resp.Status)
} }
return body, err body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
} }
return nil, nil return body, nil
} }
// parseConfig loads a TOML configuration from a provided path and // parseConfig loads a TOML configuration from a provided path and

View File

@ -383,7 +383,7 @@ func TestURLRetries3Fails(t *testing.T) {
})) }))
defer ts.Close() defer ts.Close()
expected := fmt.Sprintf("error loading config file %s: retry 3 of 3 failed to retrieve remote config: 404 Not Found", ts.URL) expected := fmt.Sprintf("error loading config file %s: failed to fetch HTTP config: 404 Not Found", ts.URL)
c := NewConfig() c := NewConfig()
err := c.LoadConfig(ts.URL) err := c.LoadConfig(ts.URL)