Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/config/config_test.go
4094 views
1
package config
2
3
import (
4
"flag"
5
"net/url"
6
"strings"
7
"testing"
8
"time"
9
10
commonCfg "github.com/prometheus/common/config"
11
12
"github.com/stretchr/testify/assert"
13
14
"github.com/grafana/agent/pkg/metrics"
15
"github.com/grafana/agent/pkg/metrics/instance"
16
"github.com/grafana/agent/pkg/server"
17
"github.com/grafana/agent/pkg/util"
18
"github.com/prometheus/common/model"
19
promCfg "github.com/prometheus/prometheus/config"
20
"github.com/prometheus/prometheus/model/labels"
21
"github.com/stretchr/testify/require"
22
"gopkg.in/yaml.v2"
23
)
24
25
// TestConfig_FlagDefaults makes sure that default values of flags are kept
26
// when parsing the config.
27
func TestConfig_FlagDefaults(t *testing.T) {
28
cfg := `
29
metrics:
30
wal_directory: /tmp/wal
31
global:
32
scrape_timeout: 33s`
33
34
fs := flag.NewFlagSet("test", flag.ExitOnError)
35
c, err := load(fs, []string{"-config.file", "test"}, func(_, _ string, _ bool, c *Config) error {
36
return LoadBytes([]byte(cfg), false, c)
37
})
38
require.NoError(t, err)
39
require.NotEmpty(t, c.Metrics.ServiceConfig.Lifecycler.InfNames)
40
require.NotZero(t, c.Metrics.ServiceConfig.Lifecycler.NumTokens)
41
require.NotZero(t, c.Metrics.ServiceConfig.Lifecycler.HeartbeatPeriod)
42
require.True(t, c.ServerFlags.RegisterInstrumentation)
43
}
44
45
// TestConfig_ConfigAPIFlag makes sure that the read API flag is passed
46
// when parsing the config.
47
func TestConfig_ConfigAPIFlag(t *testing.T) {
48
t.Run("Disabled", func(t *testing.T) {
49
cfg := `{}`
50
fs := flag.NewFlagSet("test", flag.ExitOnError)
51
c, err := load(fs, []string{"-config.file", "test"}, func(_, _ string, _ bool, c *Config) error {
52
return LoadBytes([]byte(cfg), false, c)
53
})
54
require.NoError(t, err)
55
require.False(t, c.EnableConfigEndpoints)
56
require.False(t, c.Metrics.ServiceConfig.APIEnableGetConfiguration)
57
})
58
t.Run("Enabled", func(t *testing.T) {
59
cfg := `{}`
60
fs := flag.NewFlagSet("test", flag.ExitOnError)
61
c, err := load(fs, []string{"-config.file", "test", "-config.enable-read-api"}, func(_, _ string, _ bool, c *Config) error {
62
return LoadBytes([]byte(cfg), false, c)
63
})
64
require.NoError(t, err)
65
require.True(t, c.EnableConfigEndpoints)
66
require.True(t, c.Metrics.ServiceConfig.APIEnableGetConfiguration)
67
})
68
}
69
70
func TestConfig_OverrideDefaultsOnLoad(t *testing.T) {
71
cfg := `
72
metrics:
73
wal_directory: /tmp/wal
74
global:
75
scrape_timeout: 33s`
76
expect := instance.GlobalConfig{
77
Prometheus: promCfg.GlobalConfig{
78
ScrapeInterval: model.Duration(1 * time.Minute),
79
ScrapeTimeout: model.Duration(33 * time.Second),
80
EvaluationInterval: model.Duration(1 * time.Minute),
81
},
82
}
83
84
fs := flag.NewFlagSet("test", flag.ExitOnError)
85
c, err := load(fs, []string{"-config.file", "test"}, func(_, _ string, _ bool, c *Config) error {
86
return LoadBytes([]byte(cfg), false, c)
87
})
88
require.NoError(t, err)
89
require.Equal(t, expect, c.Metrics.Global)
90
}
91
92
func TestConfig_OverrideByEnvironmentOnLoad(t *testing.T) {
93
cfg := `
94
metrics:
95
wal_directory: /tmp/wal
96
global:
97
scrape_timeout: ${SCRAPE_TIMEOUT}`
98
expect := instance.GlobalConfig{
99
Prometheus: promCfg.GlobalConfig{
100
ScrapeInterval: model.Duration(1 * time.Minute),
101
ScrapeTimeout: model.Duration(33 * time.Second),
102
EvaluationInterval: model.Duration(1 * time.Minute),
103
},
104
}
105
t.Setenv("SCRAPE_TIMEOUT", "33s")
106
107
fs := flag.NewFlagSet("test", flag.ExitOnError)
108
c, err := load(fs, []string{"-config.file", "test"}, func(_, _ string, _ bool, c *Config) error {
109
return LoadBytes([]byte(cfg), true, c)
110
})
111
require.NoError(t, err)
112
require.Equal(t, expect, c.Metrics.Global)
113
}
114
115
func TestConfig_OverrideByEnvironmentOnLoad_NoDigits(t *testing.T) {
116
cfg := `
117
metrics:
118
wal_directory: /tmp/wal
119
global:
120
external_labels:
121
foo: ${1}`
122
expect := labels.Labels{{Name: "foo", Value: "${1}"}}
123
124
fs := flag.NewFlagSet("test", flag.ExitOnError)
125
c, err := load(fs, []string{"-config.file", "test"}, func(_, _ string, _ bool, c *Config) error {
126
return LoadBytes([]byte(cfg), true, c)
127
})
128
require.NoError(t, err)
129
require.Equal(t, expect, c.Metrics.Global.Prometheus.ExternalLabels)
130
}
131
132
func TestConfig_FlagsAreAccepted(t *testing.T) {
133
cfg := `
134
metrics:
135
global:
136
scrape_timeout: 33s`
137
138
fs := flag.NewFlagSet("test", flag.ExitOnError)
139
args := []string{
140
"-config.file", "test",
141
"-metrics.wal-directory", "/tmp/wal",
142
"-config.expand-env",
143
}
144
145
c, err := load(fs, args, func(_, _ string, _ bool, c *Config) error {
146
return LoadBytes([]byte(cfg), false, c)
147
})
148
require.NoError(t, err)
149
require.Equal(t, "/tmp/wal", c.Metrics.WALDir)
150
}
151
152
func TestConfig_StrictYamlParsing(t *testing.T) {
153
t.Run("duplicate key", func(t *testing.T) {
154
cfg := `
155
metrics:
156
wal_directory: /tmp/wal
157
global:
158
scrape_timeout: 10s
159
scrape_timeout: 15s`
160
var c Config
161
err := LoadBytes([]byte(cfg), false, &c)
162
require.Error(t, err)
163
})
164
165
t.Run("non existing key", func(t *testing.T) {
166
cfg := `
167
metrics:
168
wal_directory: /tmp/wal
169
global:
170
scrape_timeout: 10s`
171
var c Config
172
err := LoadBytes([]byte(cfg), false, &c)
173
require.Error(t, err)
174
})
175
}
176
177
func TestConfig_Defaults(t *testing.T) {
178
var c Config
179
err := LoadBytes([]byte(`{}`), false, &c)
180
require.NoError(t, err)
181
182
require.Equal(t, metrics.DefaultConfig, c.Metrics)
183
require.Equal(t, DefaultVersionedIntegrations(), c.Integrations)
184
}
185
186
func TestConfig_TracesLokiValidates(t *testing.T) {
187
tests := []struct {
188
cfg string
189
}{
190
{
191
cfg: `
192
loki:
193
configs:
194
- name: default
195
positions:
196
filename: /tmp/positions.yaml
197
clients:
198
- url: http://loki:3100/loki/api/v1/push
199
traces:
200
configs:
201
- name: default
202
automatic_logging:
203
backend: loki
204
loki_name: default
205
spans: true`,
206
},
207
{
208
cfg: `
209
loki:
210
configs:
211
- name: default
212
positions:
213
filename: /tmp/positions.yaml
214
clients:
215
- url: http://loki:3100/loki/api/v1/push
216
traces:
217
configs:
218
- name: default
219
automatic_logging:
220
backend: stdout
221
loki_name: doesnt_exist
222
spans: true`,
223
},
224
}
225
226
for _, tc := range tests {
227
fs := flag.NewFlagSet("test", flag.ExitOnError)
228
_, err := load(fs, []string{"-config.file", "test"}, func(_, _ string, _ bool, c *Config) error {
229
return LoadBytes([]byte(tc.cfg), false, c)
230
})
231
232
require.NoError(t, err)
233
}
234
}
235
236
func TestConfig_LokiNameMigration(t *testing.T) {
237
input := util.Untab(`
238
loki:
239
configs:
240
- name: foo
241
positions:
242
filename: /tmp/positions.yaml
243
clients:
244
- url: http://loki:3100/loki/api/v1/push
245
`)
246
var cfg Config
247
require.NoError(t, LoadBytes([]byte(input), false, &cfg))
248
require.NoError(t, cfg.Validate(nil))
249
250
require.NotNil(t, cfg.Logs)
251
require.Equal(t, "foo", cfg.Logs.Configs[0].Name)
252
require.Equal(t, []string{"`loki` has been deprecated in favor of `logs`"}, cfg.Deprecations)
253
}
254
255
func TestConfig_PrometheusNonNil(t *testing.T) {
256
tt := []struct {
257
name string
258
input string
259
}{
260
{
261
name: "missing",
262
input: `{}`,
263
},
264
{
265
name: "null",
266
input: `metrics: null`,
267
},
268
}
269
270
for _, tc := range tt {
271
t.Run(tc.name, func(t *testing.T) {
272
var cfg Config
273
require.NoError(t, LoadBytes([]byte(tc.input), false, &cfg))
274
require.NoError(t, cfg.Validate(nil))
275
276
require.NotNil(t, cfg.Metrics)
277
})
278
}
279
}
280
281
func TestConfig_PrometheusNameMigration(t *testing.T) {
282
input := util.Untab(`
283
prometheus:
284
wal_directory: /tmp
285
configs:
286
- name: default
287
`)
288
var cfg Config
289
require.NoError(t, LoadBytes([]byte(input), false, &cfg))
290
require.NoError(t, cfg.Validate(nil))
291
292
require.Equal(t, "default", cfg.Metrics.Configs[0].Name)
293
require.Equal(t, "/tmp", cfg.Metrics.WALDir)
294
require.Equal(t, []string{"`prometheus` has been deprecated in favor of `metrics`"}, cfg.Deprecations)
295
}
296
297
func TestConfig_TracesLokiFailsValidation(t *testing.T) {
298
tests := []struct {
299
cfg string
300
expectedError string
301
}{
302
{
303
cfg: `
304
loki:
305
configs:
306
- name: foo
307
positions:
308
filename: /tmp/positions.yaml
309
clients:
310
- url: http://loki:3100/loki/api/v1/push
311
traces:
312
configs:
313
- name: default
314
automatic_logging:
315
backend: logs_instance
316
logs_instance_name: default
317
spans: true`,
318
expectedError: "error in config file: failed to validate automatic_logging for traces config default: specified logs config default not found in agent config",
319
},
320
}
321
322
for _, tc := range tests {
323
fs := flag.NewFlagSet("test", flag.ExitOnError)
324
_, err := load(fs, []string{"-config.file", "test"}, func(_, _ string, _ bool, c *Config) error {
325
return LoadBytes([]byte(tc.cfg), false, c)
326
})
327
328
require.EqualError(t, err, tc.expectedError)
329
}
330
}
331
332
func TestConfig_TempoNameMigration(t *testing.T) {
333
input := util.Untab(`
334
tempo:
335
configs:
336
- name: default
337
automatic_logging:
338
backend: stdout
339
loki_name: doesnt_exist
340
spans: true`)
341
var cfg Config
342
require.NoError(t, LoadBytes([]byte(input), false, &cfg))
343
require.NoError(t, cfg.Validate(nil))
344
345
require.NotNil(t, cfg.Traces)
346
347
require.Equal(t, "default", cfg.Traces.Configs[0].Name)
348
require.Equal(t, []string{"`tempo` has been deprecated in favor of `traces`"}, cfg.Deprecations)
349
}
350
351
func TestConfig_TempoTracesDuplicateMigration(t *testing.T) {
352
input := util.Untab(`
353
traces:
354
configs:
355
- name: default
356
automatic_logging:
357
backend: stdout
358
loki_name: doesnt_exist
359
spans: true
360
tempo:
361
configs:
362
- name: default
363
automatic_logging:
364
backend: stdout
365
loki_name: doesnt_exist
366
spans: true`)
367
var cfg Config
368
require.EqualError(t, LoadBytes([]byte(input), false, &cfg), "at most one of tempo and traces should be specified")
369
}
370
371
func TestConfig_ExpandEnvRegex(t *testing.T) {
372
cfg := `
373
logs:
374
configs:
375
- name: default
376
positions:
377
filename: /tmp/positions.yaml
378
scrape_configs:
379
- job_name: test
380
pipeline_stages:
381
- regex:
382
source: filename
383
expression: '\\temp\\Logs\\(?P<log_app>.+?)\\'`
384
fs := flag.NewFlagSet("test", flag.ExitOnError)
385
myCfg, err := load(fs, []string{"-config.file", "test"}, func(_, _ string, _ bool, c *Config) error {
386
return LoadBytes([]byte(cfg), true, c)
387
})
388
require.NoError(t, err)
389
pipelineStages := myCfg.Logs.Configs[0].ScrapeConfig[0].PipelineStages[0].(map[interface{}]interface{})
390
expected := `\\temp\\Logs\\(?P<log_app>.+?)\\`
391
require.Equal(t, expected, pipelineStages["expression"].(string))
392
}
393
394
func TestConfig_ObscureSecrets(t *testing.T) {
395
cfgText := `
396
metrics:
397
wal_directory: /tmp
398
scraping_service:
399
enabled: true
400
kvstore:
401
store: consul
402
consul:
403
acl_token: verysecret
404
lifecycler:
405
ring:
406
kvstore:
407
store: consul
408
consul:
409
acl_token: verysecret
410
`
411
412
var cfg Config
413
require.NoError(t, LoadBytes([]byte(cfgText), false, &cfg))
414
415
require.Equal(t, "verysecret", cfg.Metrics.ServiceConfig.KVStore.Consul.ACLToken.String())
416
require.Equal(t, "verysecret", cfg.Metrics.ServiceConfig.Lifecycler.RingConfig.KVStore.Consul.ACLToken.String())
417
418
bb, err := yaml.Marshal(&cfg)
419
require.NoError(t, err)
420
421
require.False(t, strings.Contains(string(bb), "verysecret"), "secrets did not get obscured")
422
require.True(t, strings.Contains(string(bb), "********"), "secrets did not get obscured properly")
423
424
// Re-validate that the config object has not changed
425
require.Equal(t, "verysecret", cfg.Metrics.ServiceConfig.KVStore.Consul.ACLToken.String())
426
require.Equal(t, "verysecret", cfg.Metrics.ServiceConfig.Lifecycler.RingConfig.KVStore.Consul.ACLToken.String())
427
}
428
429
func TestConfig_RemoteWriteDefaults(t *testing.T) {
430
cfg := `
431
metrics:
432
global:
433
remote_write:
434
- name: "foo"
435
url: "https://test/url"`
436
437
var c Config
438
err := LoadBytes([]byte(cfg), false, &c)
439
require.NoError(t, err)
440
441
expected := &promCfg.DefaultRemoteWriteConfig
442
expected.Name = "foo"
443
testURL, _ := url.Parse("https://test/url")
444
expected.URL = &commonCfg.URL{
445
URL: testURL,
446
}
447
require.Equal(t, expected, c.Metrics.Global.RemoteWrite[0])
448
require.True(t, c.Metrics.Global.RemoteWrite[0].SendExemplars)
449
}
450
451
func TestAgent_OmitEmptyFields(t *testing.T) {
452
var cfg Config
453
yml, err := yaml.Marshal(&cfg)
454
require.NoError(t, err)
455
require.Equal(t, "{}\n", string(yml))
456
}
457
458
func TestAgentManagement_MergeEffectiveConfig(t *testing.T) {
459
initialCfg := `
460
server:
461
log_level: info
462
logs:
463
positions_directory: /tmp
464
agent_management:
465
host: "localhost"
466
basic_auth:
467
username: "initial_user"
468
protocol: "http"
469
polling_interval: "1m"
470
remote_configuration:
471
namespace: "new_namespace"
472
cache_location: "/etc"`
473
474
remoteCfg := `
475
server:
476
log_level: debug
477
metrics:
478
wal_directory: /tmp
479
global:
480
scrape_interval: 5m
481
integrations:
482
scrape_integrations: true
483
484
agent_management:
485
host: "localhost:80"
486
basic_auth:
487
username: "new_user"
488
protocol: "http"
489
polling_interval: "10s"
490
remote_configuration:
491
namespace: "new_namespace"
492
cache_location: "/etc"`
493
494
var ic, rc Config
495
err := LoadBytes([]byte(initialCfg), false, &ic)
496
assert.NoError(t, err)
497
err = LoadBytes([]byte(remoteCfg), false, &rc)
498
assert.NoError(t, err)
499
500
// keep a copy of the initial config's agent management block to ensure it isn't
501
// overwritten by the remote config's
502
initialAgentManagement := ic.AgentManagement
503
mergeEffectiveConfig(&ic, &rc)
504
505
// agent_management configuration should not be overwritten by the remote config
506
assert.Equal(t, initialAgentManagement, ic.AgentManagement)
507
508
// since these elements are purposefully different for the previous portion of the test,
509
// unset them before comparing the rest of the config
510
ic.AgentManagement = AgentManagementConfig{}
511
rc.AgentManagement = AgentManagementConfig{}
512
513
assert.True(t, util.CompareYAML(ic, rc))
514
}
515
516
func TestConfig_EmptyServerConfigFails(t *testing.T) {
517
// Since we are testing defaults via config.Load, we need a file instead of a string.
518
// This test file has an empty server stanza, we expect default values out.
519
defaultServerCfg := server.DefaultConfig()
520
logger := server.NewLogger(&defaultServerCfg)
521
fs := flag.NewFlagSet("", flag.ExitOnError)
522
_, err := Load(fs, []string{"--config.file", "./testdata/server_empty.yml"}, logger)
523
require.Error(t, err)
524
}
525
526