Skip to content

Commit cff3e40

Browse files
authored
Heartbeat autodiscover (#8415)
Autodiscover support for Heartbeat + tests
1 parent 694e010 commit cff3e40

File tree

8 files changed

+139
-21
lines changed

8 files changed

+139
-21
lines changed

CHANGELOG.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ https://github.com/elastic/beats/compare/v6.4.0...master[Check the HEAD diff]
2121

2222
*Heartbeat*
2323

24+
- Added autodiscovery support {pull}8415[8415]
25+
2426
*Metricbeat*
2527

2628
*Packetbeat*

heartbeat/beater/heartbeat.go

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/elastic/beats/heartbeat/config"
2727
"github.com/elastic/beats/heartbeat/monitors"
2828
"github.com/elastic/beats/heartbeat/scheduler"
29+
"github.com/elastic/beats/libbeat/autodiscover"
2930
"github.com/elastic/beats/libbeat/beat"
3031
"github.com/elastic/beats/libbeat/cfgfile"
3132
"github.com/elastic/beats/libbeat/common"
@@ -39,6 +40,8 @@ type Heartbeat struct {
3940
config config.Config
4041
scheduler *scheduler.Scheduler
4142
monitorReloader *cfgfile.Reloader
43+
dynamicFactory *monitors.RunnerFactory
44+
autodiscover *autodiscover.Autodiscover
4245
}
4346

4447
// New creates a new heartbeat.
@@ -64,6 +67,8 @@ func New(b *beat.Beat, rawConfig *common.Config) (beat.Beater, error) {
6467
done: make(chan struct{}),
6568
config: parsedConfig,
6669
scheduler: scheduler,
70+
// dynamicFactory is the factory used for dynamic configs, e.g. autodiscover / reload
71+
dynamicFactory: monitors.NewFactory(scheduler, false),
6772
}
6873
return bt, nil
6974
}
@@ -81,12 +86,22 @@ func (bt *Heartbeat) Run(b *beat.Beat) error {
8186
bt.monitorReloader = cfgfile.NewReloader(b.Publisher, bt.config.ConfigMonitors)
8287
defer bt.monitorReloader.Stop()
8388

84-
err := bt.RunDynamicMonitors(b)
89+
err := bt.RunReloadableMonitors(b)
8590
if err != nil {
8691
return err
8792
}
8893
}
8994

95+
if bt.config.Autodiscover != nil {
96+
bt.autodiscover, err = bt.makeAutodiscover(b)
97+
if err != nil {
98+
return err
99+
}
100+
101+
bt.autodiscover.Start()
102+
defer bt.autodiscover.Stop()
103+
}
104+
90105
if err := bt.scheduler.Start(); err != nil {
91106
return err
92107
}
@@ -112,21 +127,31 @@ func (bt *Heartbeat) RunStaticMonitors(b *beat.Beat) error {
112127
return nil
113128
}
114129

115-
// RunDynamicMonitors runs the `heartbeat.config.monitors` portion of the yaml config if present.
116-
func (bt *Heartbeat) RunDynamicMonitors(b *beat.Beat) (err error) {
117-
factory := monitors.NewFactory(bt.scheduler, false)
118-
130+
// RunReloadableMonitors runs the `heartbeat.config.monitors` portion of the yaml config if present.
131+
func (bt *Heartbeat) RunReloadableMonitors(b *beat.Beat) (err error) {
119132
// Check monitor configs
120-
if err := bt.monitorReloader.Check(factory); err != nil {
133+
if err := bt.monitorReloader.Check(bt.dynamicFactory); err != nil {
121134
return err
122135
}
123136

124137
// Execute the monitor
125-
go bt.monitorReloader.Run(factory)
138+
go bt.monitorReloader.Run(bt.dynamicFactory)
126139

127140
return nil
128141
}
129142

143+
// makeAutodiscover creates an autodiscover object ready to be started.
144+
func (bt *Heartbeat) makeAutodiscover(b *beat.Beat) (*autodiscover.Autodiscover, error) {
145+
adapter := autodiscover.NewFactoryAdapter(bt.dynamicFactory)
146+
147+
ad, err := autodiscover.NewAutodiscover("heartbeat", b.Publisher, adapter, bt.config.Autodiscover)
148+
if err != nil {
149+
return nil, err
150+
}
151+
152+
return ad, nil
153+
}
154+
130155
// Stop stops the beat.
131156
func (bt *Heartbeat) Stop() {
132157
close(bt.done)

heartbeat/config/config.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,17 @@
2121
package config
2222

2323
import (
24+
"github.com/elastic/beats/libbeat/autodiscover"
2425
"github.com/elastic/beats/libbeat/common"
2526
)
2627

2728
// Config defines the structure of heartbeat.yml.
2829
type Config struct {
2930
// Modules is a list of module specific configuration data.
30-
Monitors []*common.Config `config:"monitors"`
31-
ConfigMonitors *common.Config `config:"config.monitors"`
32-
Scheduler Scheduler `config:"scheduler"`
31+
Monitors []*common.Config `config:"monitors"`
32+
ConfigMonitors *common.Config `config:"config.monitors"`
33+
Scheduler Scheduler `config:"scheduler"`
34+
Autodiscover *autodiscover.Config `config:"autodiscover"`
3335
}
3436

3537
// Scheduler defines the syntax of a heartbeat.yml scheduler block.

heartbeat/tests/system/config/heartbeat.yml.j2

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
logging.level: debug
2+
13
heartbeat.monitors:
24
{% for monitor in monitors -%}
35
- type: {{ monitor.type }}
@@ -40,6 +42,19 @@ heartbeat.config.monitors:
4042
reload.enabled: {{ reload|default("false")}}
4143
{% endif -%}
4244

45+
{% if autodiscover %}
46+
heartbeat.autodiscover:
47+
providers:
48+
{%- for provider, settings in autodiscover.items() %}
49+
- type: {{provider}}
50+
{%- if settings %}
51+
{%- for k, v in settings.items() %}
52+
{{k}}: {{v | default([])}}
53+
{%- endfor %}
54+
{%- endif %}
55+
{%- endfor %}
56+
{% endif %}
57+
4358
{%- if shipper_name %}
4459
name: {{ shipper_name }}
4560
{% endif %}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import os
2+
from heartbeat import BaseTest
3+
import unittest
4+
import re
5+
6+
from beat.beat import INTEGRATION_TESTS
7+
8+
9+
class TestAutodiscover(BaseTest):
10+
"""
11+
Test heartbeat autodiscover
12+
"""
13+
@unittest.skipIf(not INTEGRATION_TESTS or
14+
os.getenv("TESTING_ENVIRONMENT") == "2x",
15+
"integration test not available on 2.x")
16+
def test_docker(self):
17+
"""
18+
Test docker autodiscover starts modules from templates
19+
"""
20+
import docker
21+
docker_client = docker.from_env()
22+
23+
self.render_config_template(
24+
autodiscover={
25+
'docker': {
26+
'templates': '''
27+
- condition:
28+
contains.docker.container.image: redis
29+
config:
30+
- type: tcp
31+
hosts: ["${data.host}:${data.port}"]
32+
schedule: "@every 1s"
33+
timeout: 1s
34+
''',
35+
},
36+
},
37+
)
38+
39+
proc = self.start_beat()
40+
41+
self.wait_until(lambda: self.log_contains(
42+
re.compile('autodiscover.+Got a start event:', re.I)))
43+
44+
self.wait_until(lambda: self.output_count(lambda x: x >= 1))
45+
46+
output = self.read_output_json()
47+
proc.check_kill_and_wait()
48+
49+
matched = False
50+
matcher = re.compile("redis", re.I)
51+
for i, container in enumerate(docker_client.containers.list()):
52+
for tag in container.image.tags:
53+
if matcher.search(tag):
54+
network_settings = container.attrs['NetworkSettings']
55+
host = network_settings['Networks'].values()[
56+
0]['IPAddress']
57+
port = network_settings['Ports'].keys()[0].split("/")[0]
58+
# Check metadata is added
59+
expected = 'tcp-tcp@%s:%s' % (host, port)
60+
actual = output[0]['monitor']['id']
61+
if expected == actual:
62+
matched = True
63+
64+
assert matched

metricbeat/autodiscover/autodiscover.go renamed to libbeat/autodiscover/factoryadapter.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,20 @@ import (
2626
"github.com/elastic/beats/libbeat/common/bus"
2727
)
2828

29-
// AutodiscoverAdapter for Metricbeat modules
30-
type AutodiscoverAdapter struct {
29+
// FactoryAdapter is an adapter that works with any cfgfile.RunnerFactory.
30+
type FactoryAdapter struct {
3131
factory cfgfile.RunnerFactory
3232
}
3333

34-
// NewAutodiscoverAdapter builds and returns an autodiscover adapter for Metricbeat modules
35-
func NewAutodiscoverAdapter(factory cfgfile.RunnerFactory) *AutodiscoverAdapter {
36-
return &AutodiscoverAdapter{
34+
// NewFactoryAdapter builds and returns an autodiscover adapter that works with any cfgfile.RunnerFactory.
35+
func NewFactoryAdapter(factory cfgfile.RunnerFactory) *FactoryAdapter {
36+
return &FactoryAdapter{
3737
factory: factory,
3838
}
3939
}
4040

4141
// CreateConfig generates a valid list of configs from the given event, the received event will have all keys defined by `StartFilter`
42-
func (m *AutodiscoverAdapter) CreateConfig(e bus.Event) ([]*common.Config, error) {
42+
func (m *FactoryAdapter) CreateConfig(e bus.Event) ([]*common.Config, error) {
4343
config, ok := e["config"].([]*common.Config)
4444
if !ok {
4545
return nil, errors.New("Got a wrong value in event `config` key")
@@ -48,16 +48,16 @@ func (m *AutodiscoverAdapter) CreateConfig(e bus.Event) ([]*common.Config, error
4848
}
4949

5050
// CheckConfig tests given config to check if it will work or not, returns errors in case it won't work
51-
func (m *AutodiscoverAdapter) CheckConfig(c *common.Config) error {
51+
func (m *FactoryAdapter) CheckConfig(c *common.Config) error {
5252
return m.factory.CheckConfig(c)
5353
}
5454

5555
// Create a module or prospector from the given config
56-
func (m *AutodiscoverAdapter) Create(p beat.Pipeline, c *common.Config, meta *common.MapStrPointer) (cfgfile.Runner, error) {
56+
func (m *FactoryAdapter) Create(p beat.Pipeline, c *common.Config, meta *common.MapStrPointer) (cfgfile.Runner, error) {
5757
return m.factory.Create(p, c, meta)
5858
}
5959

6060
// EventFilter returns the bus filter to retrieve runner start/stop triggering events
61-
func (m *AutodiscoverAdapter) EventFilter() []string {
61+
func (m *FactoryAdapter) EventFilter() []string {
6262
return []string{"config"}
6363
}

libbeat/tests/system/beat/beat.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import time
1111
import yaml
1212
import hashlib
13+
import re
1314
from datetime import datetime, timedelta
1415

1516
from .compose import ComposeMixin
@@ -22,6 +23,8 @@
2223

2324
yaml_cache = {}
2425

26+
REGEXP_TYPE = type(re.compile("t"))
27+
2528

2629
class TimeoutError(Exception):
2730
pass
@@ -359,6 +362,7 @@ def log_contains_count(self, msg, logfile=None, ignore_case=False):
359362
"""
360363
Returns the number of appearances of the given string in the log file
361364
"""
365+
is_regexp = type(msg) == REGEXP_TYPE
362366

363367
counter = 0
364368
if ignore_case:
@@ -371,6 +375,10 @@ def log_contains_count(self, msg, logfile=None, ignore_case=False):
371375
try:
372376
with open(os.path.join(self.working_dir, logfile), "r") as f:
373377
for line in f:
378+
if is_regexp:
379+
if msg.search(line) is not None:
380+
counter = counter + 1
381+
continue
374382
if ignore_case:
375383
line = line.lower()
376384
if line.find(msg) >= 0:

metricbeat/beater/metricbeat.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ import (
3232
"github.com/elastic/beats/libbeat/common"
3333
"github.com/elastic/beats/libbeat/common/cfgwarn"
3434
"github.com/elastic/beats/libbeat/logp"
35-
mbautodiscover "github.com/elastic/beats/metricbeat/autodiscover"
3635
"github.com/elastic/beats/metricbeat/mb"
3736
"github.com/elastic/beats/metricbeat/mb/module"
3837

38+
// Add autodiscover builders / appenders
39+
_ "github.com/elastic/beats/metricbeat/autodiscover"
40+
3941
// Add metricbeat default processors
4042
_ "github.com/elastic/beats/metricbeat/processor/add_kubernetes_metadata"
4143
)
@@ -172,7 +174,7 @@ func newMetricbeat(b *beat.Beat, c *common.Config, options ...Option) (*Metricbe
172174
if config.Autodiscover != nil {
173175
var err error
174176
factory := module.NewFactory(metricbeat.moduleOptions...)
175-
adapter := mbautodiscover.NewAutodiscoverAdapter(factory)
177+
adapter := autodiscover.NewFactoryAdapter(factory)
176178
metricbeat.autodiscover, err = autodiscover.NewAutodiscover("metricbeat", b.Publisher, adapter, config.Autodiscover)
177179
if err != nil {
178180
return nil, err

0 commit comments

Comments
 (0)