Merge ~aieri/charm-prometheus-blackbox-exporter:bb-sub into ~prometheus-charmers/charm-prometheus-blackbox-exporter:master

Proposed by Andrea Ieri
Status: Rejected
Rejected by: Haw Loeung
Proposed branch: ~aieri/charm-prometheus-blackbox-exporter:bb-sub
Merge into: ~prometheus-charmers/charm-prometheus-blackbox-exporter:master
Diff against target: 282 lines (+192/-15)
5 files modified
config.yaml (+7/-0)
layer.yaml (+7/-2)
metadata.yaml (+9/-2)
reactive/prometheus-blackbox-exporter.py (+167/-11)
wheelhouse.txt (+2/-0)
Reviewer Review Type Date Requested Status
Alvaro Uria (community) Disapprove
BootStack Reviewers mr tracking; do not claim Pending
BootStack Reviewers Pending
BootStack Reviewers Pending
Canonical IS Reviewers Pending
Canonical IS Reviewers Pending
Review via email: mp+372592@code.launchpad.net

This proposal supersedes a proposal from 2019-09-06.

Commit message

Blackbox exporter rewritten as subordinate

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : Posted in a previous version of this proposal

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : Posted in a previous version of this proposal

Unable to determine commit message from repository - please click "Set commit message" and enter the commit message manually.

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Unable to determine commit message from repository - please click "Set commit message" and enter the commit message manually.

7927384... by Andrea Ieri

Add the juju-info interface explicitly

413057f... by Andrea Ieri

Publish the external IP in case we're running cross-model relations

64d1422... by Andrea Ieri

Filter out uninteresting IPs and interfaces

881b419... by Andrea Ieri

Lookup source ips for all probes and provide the address over the relation

a458c6a... by Andrea Ieri

Noop readability refactor

Revision history for this message
Alvaro Uria (aluria) wrote :

A new charm needs to be created (ie. prometheus-blackbox-peer-exporter) for this MP. The current charm is non-subordinate and used by IS, while the current fix makes the charm a subordinate.

I will talk to JL to create that new charm and create MPs against it.

review: Disapprove

Unmerged commits

a458c6a... by Andrea Ieri

Noop readability refactor

881b419... by Andrea Ieri

Lookup source ips for all probes and provide the address over the relation

64d1422... by Andrea Ieri

Filter out uninteresting IPs and interfaces

413057f... by Andrea Ieri

Publish the external IP in case we're running cross-model relations

7927384... by Andrea Ieri

Add the juju-info interface explicitly

f631577... by Andrea Ieri

Fix subordinate endpoint name

af6e21c... by Andrea Ieri

Cosmetic changes to layer/metadata. Make charm proof a little happier.

4bd9ed2... by Andrea Ieri

Add a wheelhouse.txt to cover python dependencies

60d1957... by Diko Parvanov

Added icmp checks and fixed hooks/peer relations

347f56e... by Diko Parvanov

Added multiple networks checks + MTU config option

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/config.yaml b/config.yaml
2index d1dc24d..fc04ec0 100644
3--- a/config.yaml
4+++ b/config.yaml
5@@ -16,6 +16,13 @@ options:
6 icmp:
7 prober: icmp
8 timeout: 10s
9+ icmp:
10+ preferred_ip_protocol: "ip4"
11+ payload_size: 1472
12 type: string
13 description: |
14 Blackbox exporter configuratin in raw YAML format
15+ scrape-interval:
16+ type: string
17+ default: "60s"
18+ description: Set the blackbox exporter scrape jobs custom interval.
19diff --git a/layer.yaml b/layer.yaml
20index ddd95fc..6036e6d 100644
21--- a/layer.yaml
22+++ b/layer.yaml
23@@ -1,6 +1,11 @@
24-includes: ['layer:basic', 'interface:http', 'layer:snap']
25+includes:
26+ - 'layer:basic'
27+ - "interface:juju-info"
28+ - 'interface:http'
29+ - 'interface:peer-discovery'
30 repo: 'https://git.launchpad.net/prometheus-blackbox-exporter-charm'
31-ignore: ['.*.swp' ]
32+ignore:
33+ - '.*.swp'
34 options:
35 basic:
36 use_venv: true
37diff --git a/metadata.yaml b/metadata.yaml
38index 4f9228b..89d3c6e 100644
39--- a/metadata.yaml
40+++ b/metadata.yaml
41@@ -1,4 +1,5 @@
42 name: prometheus-blackbox-exporter
43+display-name: Prometheus Blackbox Exporter
44 summary: Blackbox exporter for Prometheus
45 maintainer: Jacek Nykis <jacek.nykis@canonical.com>
46 description: |
47@@ -7,9 +8,15 @@ description: |
48 tags:
49 - monitoring
50 series:
51- - xenial
52 - bionic
53-subordinate: false
54+subordinate: true
55 provides:
56 blackbox-exporter:
57 interface: http
58+requires:
59+ general-info:
60+ interface: juju-info
61+ scope: container
62+peers:
63+ blackbox-peer:
64+ interface: peer-discovery
65diff --git a/reactive/prometheus-blackbox-exporter.py b/reactive/prometheus-blackbox-exporter.py
66index a5d792a..7d4e877 100644
67--- a/reactive/prometheus-blackbox-exporter.py
68+++ b/reactive/prometheus-blackbox-exporter.py
69@@ -1,19 +1,33 @@
70+import ast
71+import re
72+import subprocess
73 import yaml
74+import sys
75
76 from charmhelpers.core import host, hookenv
77 from charmhelpers.core.templating import render
78 from charms.reactive import (
79 when, when_not, set_state, remove_state
80 )
81+from ipaddress import (
82+ IPv4Interface, IPv4Address
83+)
84+from pyroute2 import IPRoute
85+from netifaces import interfaces, ifaddresses, AF_INET
86 from charms.reactive.helpers import any_file_changed, data_changed
87-from charms.layer import snap
88+from charms.reactive import hook
89+
90+from charmhelpers.fetch import apt_install
91
92+hooks = hookenv.Hooks()
93
94-SNAP_NAME = 'prometheus-blackbox-exporter'
95-SVC_NAME = 'snap.prometheus-blackbox-exporter.daemon'
96+APT_PKG_NAME = 'prometheus-blackbox-exporter'
97+SVC_NAME = 'prometheus-blackbox-exporter'
98+EXECUTABLE = '/usr/bin/prometheus-blackbox-exporter'
99 PORT_DEF = 9115
100 BLACKBOX_EXPORTER_YML_TMPL = 'blackbox.yaml.j2'
101-CONF_FILE_PATH = '/var/snap/prometheus-blackbox-exporter/current/blackbox.yml'
102+CONF_FILE_PATH = '/etc/prometheus/blackbox.yml'
103+IFACE_BLACKLIST_PATTERN = re.compile('^(lo|virbr|docker|lxdbr|vhost|tun|tap)')
104
105
106 def templates_changed(tmpl_list):
107@@ -24,8 +38,9 @@ def templates_changed(tmpl_list):
108 def install_packages():
109 hookenv.status_set('maintenance', 'Installing software')
110 config = hookenv.config()
111- channel = config.get('snap_channel', 'stable')
112- snap.install(SNAP_NAME, channel=channel, force_dangerous=False)
113+ apt_install(APT_PKG_NAME, fatal=True)
114+ cmd = ["sudo", "setcap", "cap_net_raw+ep", EXECUTABLE]
115+ subprocess.check_output(cmd)
116 set_state('blackbox-exporter.installed')
117 set_state('blackbox-exporter.do-check-reconfig')
118
119@@ -86,9 +101,150 @@ def restart_blackbox_exporter():
120 set_state('blackbox-exporter.started')
121 remove_state('blackbox-exporter.do-restart')
122
123-
124 # Relations
125-@when('blackbox-exporter.started')
126-@when('blackbox-exporter.available') # Relation name is "blackbox-exporter"
127-def configure_blackbox_exporter_relation(target):
128- target.configure(PORT_DEF)
129+@hook('blackbox-peer-relation-{joined,departed}')
130+def configure_blackbox_exporter_relation(peers):
131+ hookenv.log('Running blackbox exporter relation.')
132+ hookenv.status_set('maintenance', 'Configuring blackbox peer relations.')
133+ config = hookenv.config()
134+
135+ icmp_targets = []
136+ tcp_targets = []
137+ networks = []
138+ for rid in hookenv.relation_ids('blackbox-peer'):
139+ for unit in hookenv.related_units(rid):
140+ unit_ports = hookenv.relation_get('unit-ports', rid=rid, unit=unit)
141+ principal_unit = hookenv.relation_get('principal-unit', rid=rid, unit=unit)
142+ unit_networks = hookenv.relation_get('unit-networks', rid=rid, unit=unit)
143+ if unit_networks is not None:
144+ unit_networks = ast.literal_eval(unit_networks)
145+ for unit_network in unit_networks:
146+ # Chcek if same network exists on this unit
147+ if unit_network['net'] in [net['net'] for net in get_unit_networks()]:
148+ networks.append(unit_network['net'])
149+ probe_dst_ip = unit_network['ip']
150+ icmp_targets.append({
151+ 'network': unit_network['net'],
152+ 'interface': unit_network['iface'],
153+ 'ip-address': probe_dst_ip,
154+ 'principal-unit': principal_unit,
155+ 'module': 'icmp',
156+ 'source-ip': _get_source_ip(probe_dst_ip)
157+ })
158+
159+ if unit_ports is not None:
160+ unit_ports = ast.literal_eval(unit_ports)
161+ for port in unit_ports:
162+ tcp_targets.append({
163+ 'ip-address': unit_network['ip'],
164+ 'port': port,
165+ 'principal-unit': principal_unit,
166+ 'module': 'tcp_connect',
167+ })
168+
169+ relation_settings = {}
170+ relation_settings['icmp_targets'] = icmp_targets
171+ relation_settings['tcp_targets'] = tcp_targets
172+ relation_settings['networks'] = networks
173+ relation_settings['ip_address'] = hookenv.unit_get('private-address')
174+ relation_settings['port'] = PORT_DEF
175+ relation_settings['job_name'] = hookenv.principal_unit()
176+ relation_settings['scrape_interval'] = config.get('scrape-interval')
177+
178+
179+ for rel_id in hookenv.relation_ids('blackbox-exporter'):
180+ relation_settings['ip_address'] = \
181+ hookenv.ingress_address(rid=rel_id, unit=hookenv.local_unit())
182+ hookenv.relation_set(relation_id=rel_id, relation_settings=relation_settings)
183+
184+ hookenv.status_set('active', 'Ready')
185+
186+
187+def _get_source_ip(destination):
188+ """
189+ Get the source ip of a connection towards destination without having to run
190+ ip r g via subprocess
191+
192+ Disclaimer: source ip configuration for the blackbox exporter is done in
193+ the module definition. To avoid having to create a module for every local
194+ address we are simply letting the exporter use whatever source IP the
195+ kernel deems appropriate. Be aware that this function is doing a route
196+ lookup, and it is not - strictly speaking - returning the source IP of the
197+ packet the blackbox exporter will generate. The two addresses should always
198+ be the same, but YMMV.
199+ """
200+ with IPRoute() as ipr:
201+ routes = ipr.route('get', dst=destination)
202+ return routes[0].get_attr('RTA_PREFSRC')
203+
204+
205+def _is_valid_ip(address):
206+ """
207+ Filter out "uninteresting" addresses
208+ """
209+ ip = IPv4Address(address.get('addr'))
210+ return not (ip.is_multicast or
211+ ip.is_reserved or
212+ ip.is_link_local or
213+ ip.is_loopback)
214+
215+
216+def _is_valid_iface(iface):
217+ """
218+ Ignore interfaces used by Docker, KVM, Contrail, etc
219+ """
220+ if IFACE_BLACKLIST_PATTERN.search(iface):
221+ return False
222+ else:
223+ return True
224+
225+
226+def get_unit_networks():
227+ networks = []
228+ for iface in filter(_is_valid_iface, interfaces()):
229+ ip_addresses = ifaddresses(iface)
230+ for ip_address in filter(_is_valid_ip, ip_addresses.get(AF_INET, [])):
231+ ip_v4 = IPv4Interface(
232+ "{}/{}".format(ip_address['addr'], ip_address['netmask'])
233+ )
234+ networks.append(
235+ {"iface": iface,
236+ "ip": str(ip_v4.ip),
237+ "net": str(ip_v4.network)}
238+ )
239+ return networks
240+
241+def get_principal_unit_open_ports():
242+ cmd = "lsof -P -iTCP -sTCP:LISTEN".split()
243+ result = subprocess.check_output(cmd)
244+ result = result.decode(sys.stdout.encoding)
245+
246+ ports = []
247+ for r in result.split('\n'):
248+ for p in r.split():
249+ if '*:' in p:
250+ ports.append(p.split(':')[1])
251+ ports = [p for p in set(ports)]
252+
253+ return ports
254+
255+@hook('blackbox-peer-relation-{joined,departed}')
256+def blackbox_peer_departed(peers):
257+ hookenv.log('Blackbox peer unit joined/departed.')
258+ set_state('blackbox-exporter.redo-peer-relation')
259+
260+@when('blackbox-exporter.redo-peer-relation')
261+def setup_blackbox_peer_relation(peers):
262+ # Set blackbox-peer relations
263+ hookenv.log('Running blackbox peer relations.')
264+ hookenv.status_set('maintenance', 'Configuring blackbox peer relations.')
265+ for rid in hookenv.relation_ids('blackbox-peer'):
266+ relation_settings = hookenv.relation_get(rid=rid, unit=hookenv.local_unit())
267+ relation_settings['principal-unit'] = hookenv.principal_unit()
268+ relation_settings['private-address'] = hookenv.unit_get('private-address')
269+ relation_settings['unit-networks'] = get_unit_networks()
270+ relation_settings['unit-ports'] = get_principal_unit_open_ports()
271+ hookenv.relation_set(relation_id=rid, relation_settings=relation_settings)
272+
273+ hookenv.status_set('active', 'Ready')
274+ remove_state('blackbox-exporter.redo-peer-relation')
275diff --git a/wheelhouse.txt b/wheelhouse.txt
276new file mode 100644
277index 0000000..ea2fb5e
278--- /dev/null
279+++ b/wheelhouse.txt
280@@ -0,0 +1,2 @@
281+netifaces
282+pyroute2

Subscribers

People subscribed via source and target branches