Merge ~xavpaice/charm-prometheus-blackbox-exporter:dev/q2-20 into charm-prometheus-blackbox-exporter:master

Proposed by Xav Paice
Status: Merged
Approved by: Paul Goins
Approved revision: ff9ed95c3e232fe6aa77fcf4235fcb0d8e0ccdf6
Merged at revision: ff9ed95c3e232fe6aa77fcf4235fcb0d8e0ccdf6
Proposed branch: ~xavpaice/charm-prometheus-blackbox-exporter:dev/q2-20
Merge into: charm-prometheus-blackbox-exporter:master
Diff against target: 975 lines (+765/-7)
19 files modified
.gitignore (+24/-0)
Makefile (+48/-0)
README.md (+33/-0)
config.yaml (+14/-0)
copyright (+17/-0)
icon.svg (+279/-0)
layer.yaml (+7/-2)
metadata.yaml (+11/-0)
reactive/prometheus_blackbox_exporter.py (+124/-5)
requirements.txt (+1/-0)
tests/functional/requirements.txt (+1/-0)
tests/functional/tests/__init__.py (+1/-0)
tests/functional/tests/bundles/bionic.yaml (+16/-0)
tests/functional/tests/bundles/focal.yaml (+16/-0)
tests/functional/tests/bundles/overlays/local-charm-overlay.yaml.j2 (+3/-0)
tests/functional/tests/bundles/xenial.yaml (+16/-0)
tests/functional/tests/test_prometheus_blackbox_exporter.py (+78/-0)
tests/functional/tests/tests.yaml (+11/-0)
tox.ini (+65/-0)
Reviewer Review Type Date Requested Status
Paul Goins Approve
Xav Paice Pending
Review via email: mp+384318@code.launchpad.net

This proposal supersedes a proposal from 2020-04-02.

To post a comment you must log in.
Revision history for this message
Xav Paice (xavpaice) wrote : Posted in a previous version of this proposal

Passed lint test, but unit tests are failing with https://pastebin.canonical.com/p/9tnFBtWrd8/, even after I'd added the report dir mentioned in the README. This is a sizable change, and while I appreciate that this is introducing tests where there were none, it would be good to be able to pass the tests introduced and know the change is good. It's likely that there's something on my test env that's different to the author's, which we should be able to document or, better, add to the Makefile etc.

From what I can see so far, the rest of the change looks good.

review: Needs Fixing
Revision history for this message
Xav Paice (xavpaice) wrote :

I've updated the change with a commit that works for functional testing and lint, and also renames the charm code itself to a name that doesn't have - (because Python).

My review now is of course a +1, but need someone else to look at it now.

Revision history for this message
Xav Paice (xavpaice) wrote :

Note: there's no unit tests as yet.

Revision history for this message
Paul Goins (vultaire) wrote :

Overall, this looks good.

I have a few comments, the most important likely being about the license, which I think requires attention. The others are things I think should be fixed but aren't really that important.

review: Needs Fixing
Revision history for this message
Xav Paice (xavpaice) wrote :

I've updated the commit with updates for the catches - entirely removed unit tests (since they're not actually present), updated license to GPLv3, and removed the totally unused flag.

Revision history for this message
Paul Goins (vultaire) wrote :

Approved!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.gitignore b/.gitignore
2new file mode 100644
3index 0000000..2dc89c9
4--- /dev/null
5+++ b/.gitignore
6@@ -0,0 +1,24 @@
7+# Byte-compiled / optimized / DLL files
8+__pycache__/
9+*.py[cod]
10+*$py.class
11+
12+# Log files
13+*.log
14+.tox/
15+.coverage
16+
17+# vi
18+.*.swp
19+
20+# pycharm
21+.idea/
22+
23+# version data
24+repo-info
25+
26+# reports
27+report/*
28+
29+# layers
30+layers/*
31\ No newline at end of file
32diff --git a/Makefile b/Makefile
33new file mode 100644
34index 0000000..949713b
35--- /dev/null
36+++ b/Makefile
37@@ -0,0 +1,48 @@
38+ifndef CHARM_BUILD_DIR
39+ CHARM_BUILD_DIR=/tmp/builds
40+endif
41+
42+help:
43+ @echo "This project supports the following targets"
44+ @echo ""
45+ @echo " make help - show this text"
46+ @echo " make submodules - make sure that the submodules are up-to-date"
47+ @echo " make lint - run flake8"
48+ @echo " make test - run the functional tests and lint"
49+ @echo " make functional - run the tests defined in the functional subdirectory"
50+ @echo " make release - build the charm"
51+ @echo " make clean - remove unneeded files"
52+ @echo ""
53+
54+submodules:
55+ @echo "Cloning submodules"
56+ @git submodule update --init --recursive
57+
58+lint:
59+ @mkdir -p report/lint/
60+ @echo "Running flake8"
61+ @tox -e lint
62+
63+test: lint functional
64+
65+functional: build
66+ @echo Executing with: CHARM_BUILD_DIR=$(CHARM_BUILD_DIR) tox -e func
67+ @CHARM_BUILD_DIR=$(CHARM_BUILD_DIR) tox -e func
68+
69+build:
70+ @echo "Building charm to base directory $(CHARM_BUILD_DIR)"
71+ @-git describe --tags > ./repo-info
72+ @CHARM_LAYERS_DIR=./layers CHARM_INTERFACES_DIR=./interfaces TERM=linux \
73+ CHARM_BUILD_DIR=$(CHARM_BUILD_DIR) charm build . --force
74+
75+release: clean build
76+ @echo "Charm is built at $(CHARM_BUILD_DIR)/prometheus-blackbox-exporter"
77+
78+clean:
79+ @echo "Cleaning files"
80+ @if [ -d .tox ] ; then rm -r .tox ; fi
81+ @if [ -d .pytest_cache ] ; then rm -r .pytest_cache ; fi
82+ @find . -iname __pycache__ -exec rm -r {} +
83+
84+# The targets below don't depend on a file
85+.PHONY: lint test functional build release clean help submodules
86diff --git a/README.md b/README.md
87index 58725ef..ffde3eb 100644
88--- a/README.md
89+++ b/README.md
90@@ -16,3 +16,36 @@ you can update the charm's configuration using:
91 To confirm configuration was set:
92
93 juju config prometheus-blackbox-exporter
94+
95+## Testing
96+
97+# This directory needs to be create in the charm path prior to testing
98+```
99+mkdir -p report/lint
100+```
101+
102+## Deployment
103+
104+# To avail of the metrics in grafana the following steps can be used
105+```
106+juju deploy grafana
107+juju deploy prometheus2
108+juju add-relation prometheus-blackbox-exporter:scrape prometheus2:target
109+juju add-relation prometheus-blackbox-exporter:dashboards grafana:dashboards
110+```
111+
112+# To setup reporting with nagios
113+```
114+juju deploy nrpe
115+juju add-relation prometheus-blackbox-exporter:nrpe-external-master nrpe:nrpe-external-master
116+```
117+
118+# Change or update dashboards
119+```
120+# To provide your own dashboards, create a zip file and attach it as a resource
121+zip grafana-dashboards.zip blackbox-simple.json blackbox-advanced.json
122+juju attach-resource prometheus-blackbox-exporter dashboards=./grafana-dashboards.zip
123+```
124+
125+# Contact Information
126+- Charm bugs: https://bugs.launchpad.net/charm-prometheus-blackbox-exporter
127diff --git a/config.yaml b/config.yaml
128index d1dc24d..2f5db13 100644
129--- a/config.yaml
130+++ b/config.yaml
131@@ -5,6 +5,20 @@ options:
132 description: |
133 If install_method is set to "snap" this option controlls channel name.
134 Supported values are: "stable", "candidate", "beta" and "edge"
135+ nagios_context:
136+ default: "juju"
137+ type: string
138+ description: |
139+ A string that will be prepended to instance name to set the host name
140+ in nagios. So for instance the hostname would be something like:
141+ juju-myservice-0
142+ If you're running multiple environments with the same services in them
143+ this allows you to differentiate between them.
144+ nagios_servicegroups:
145+ default: "juju"
146+ type: string
147+ description: |
148+ Comma separated list of nagios servicegroups
149 modules:
150 default: |
151 http_2xx:
152diff --git a/copyright b/copyright
153new file mode 100644
154index 0000000..8dddbf0
155--- /dev/null
156+++ b/copyright
157@@ -0,0 +1,17 @@
158+Format: http://dep.debian.net/deps/dep5/
159+
160+Files: *
161+Copyright: Copyright 2012-2018, Canonical Ltd., All Rights Reserved.
162+License: GPL-3
163+ This program is free software: you can redistribute it and/or modify
164+ it under the terms of the GNU General Public License as published by
165+ the Free Software Foundation, either version 3 of the License, or
166+ (at your option) any later version.
167+ .
168+ This program is distributed in the hope that it will be useful,
169+ but WITHOUT ANY WARRANTY; without even the implied warranty of
170+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
171+ GNU General Public License for more details.
172+ .
173+ You should have received a copy of the GNU General Public License
174+ along with this program. If not, see <http://www.gnu.org/licenses/>.
175diff --git a/icon.svg b/icon.svg
176new file mode 100644
177index 0000000..96a5d0c
178--- /dev/null
179+++ b/icon.svg
180@@ -0,0 +1,279 @@
181+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
182+<!-- Created with Inkscape (http://www.inkscape.org/) -->
183+
184+<svg
185+ xmlns:dc="http://purl.org/dc/elements/1.1/"
186+ xmlns:cc="http://creativecommons.org/ns#"
187+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
188+ xmlns:svg="http://www.w3.org/2000/svg"
189+ xmlns="http://www.w3.org/2000/svg"
190+ xmlns:xlink="http://www.w3.org/1999/xlink"
191+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
192+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
193+ width="96"
194+ height="96"
195+ id="svg6517"
196+ version="1.1"
197+ inkscape:version="0.48+devel r12274"
198+ sodipodi:docname="Juju_charm_icon_template.svg">
199+ <defs
200+ id="defs6519">
201+ <linearGradient
202+ inkscape:collect="always"
203+ xlink:href="#Background"
204+ id="linearGradient6461"
205+ gradientUnits="userSpaceOnUse"
206+ x1="0"
207+ y1="970.29498"
208+ x2="144"
209+ y2="970.29498"
210+ gradientTransform="matrix(0,-0.66666669,0.6660448,0,-866.25992,731.29077)" />
211+ <linearGradient
212+ id="Background">
213+ <stop
214+ id="stop4178"
215+ offset="0"
216+ style="stop-color:#b8b8b8;stop-opacity:1" />
217+ <stop
218+ id="stop4180"
219+ offset="1"
220+ style="stop-color:#c9c9c9;stop-opacity:1" />
221+ </linearGradient>
222+ <filter
223+ style="color-interpolation-filters:sRGB;"
224+ inkscape:label="Inner Shadow"
225+ id="filter1121">
226+ <feFlood
227+ flood-opacity="0.59999999999999998"
228+ flood-color="rgb(0,0,0)"
229+ result="flood"
230+ id="feFlood1123" />
231+ <feComposite
232+ in="flood"
233+ in2="SourceGraphic"
234+ operator="out"
235+ result="composite1"
236+ id="feComposite1125" />
237+ <feGaussianBlur
238+ in="composite1"
239+ stdDeviation="1"
240+ result="blur"
241+ id="feGaussianBlur1127" />
242+ <feOffset
243+ dx="0"
244+ dy="2"
245+ result="offset"
246+ id="feOffset1129" />
247+ <feComposite
248+ in="offset"
249+ in2="SourceGraphic"
250+ operator="atop"
251+ result="composite2"
252+ id="feComposite1131" />
253+ </filter>
254+ <filter
255+ style="color-interpolation-filters:sRGB;"
256+ inkscape:label="Drop Shadow"
257+ id="filter950">
258+ <feFlood
259+ flood-opacity="0.25"
260+ flood-color="rgb(0,0,0)"
261+ result="flood"
262+ id="feFlood952" />
263+ <feComposite
264+ in="flood"
265+ in2="SourceGraphic"
266+ operator="in"
267+ result="composite1"
268+ id="feComposite954" />
269+ <feGaussianBlur
270+ in="composite1"
271+ stdDeviation="1"
272+ result="blur"
273+ id="feGaussianBlur956" />
274+ <feOffset
275+ dx="0"
276+ dy="1"
277+ result="offset"
278+ id="feOffset958" />
279+ <feComposite
280+ in="SourceGraphic"
281+ in2="offset"
282+ operator="over"
283+ result="composite2"
284+ id="feComposite960" />
285+ </filter>
286+ <clipPath
287+ clipPathUnits="userSpaceOnUse"
288+ id="clipPath873">
289+ <g
290+ transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
291+ id="g875"
292+ inkscape:label="Layer 1"
293+ style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
294+ <path
295+ style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
296+ d="m 46.702703,898.22775 50.594594,0 C 138.16216,898.22775 144,904.06497 144,944.92583 l 0,50.73846 c 0,40.86071 -5.83784,46.69791 -46.702703,46.69791 l -50.594594,0 C 5.8378378,1042.3622 0,1036.525 0,995.66429 L 0,944.92583 C 0,904.06497 5.8378378,898.22775 46.702703,898.22775 Z"
297+ id="path877"
298+ inkscape:connector-curvature="0"
299+ sodipodi:nodetypes="sssssssss" />
300+ </g>
301+ </clipPath>
302+ <filter
303+ inkscape:collect="always"
304+ id="filter891"
305+ inkscape:label="Badge Shadow">
306+ <feGaussianBlur
307+ inkscape:collect="always"
308+ stdDeviation="0.71999962"
309+ id="feGaussianBlur893" />
310+ </filter>
311+ </defs>
312+ <sodipodi:namedview
313+ id="base"
314+ pagecolor="#ffffff"
315+ bordercolor="#666666"
316+ borderopacity="1.0"
317+ inkscape:pageopacity="0.0"
318+ inkscape:pageshadow="2"
319+ inkscape:zoom="4.0745362"
320+ inkscape:cx="18.514671"
321+ inkscape:cy="49.018169"
322+ inkscape:document-units="px"
323+ inkscape:current-layer="layer1"
324+ showgrid="true"
325+ fit-margin-top="0"
326+ fit-margin-left="0"
327+ fit-margin-right="0"
328+ fit-margin-bottom="0"
329+ inkscape:window-width="1920"
330+ inkscape:window-height="1029"
331+ inkscape:window-x="0"
332+ inkscape:window-y="24"
333+ inkscape:window-maximized="1"
334+ showborder="true"
335+ showguides="true"
336+ inkscape:guide-bbox="true"
337+ inkscape:showpageshadow="false">
338+ <inkscape:grid
339+ type="xygrid"
340+ id="grid821" />
341+ <sodipodi:guide
342+ orientation="1,0"
343+ position="16,48"
344+ id="guide823" />
345+ <sodipodi:guide
346+ orientation="0,1"
347+ position="64,80"
348+ id="guide825" />
349+ <sodipodi:guide
350+ orientation="1,0"
351+ position="80,40"
352+ id="guide827" />
353+ <sodipodi:guide
354+ orientation="0,1"
355+ position="64,16"
356+ id="guide829" />
357+ </sodipodi:namedview>
358+ <metadata
359+ id="metadata6522">
360+ <rdf:RDF>
361+ <cc:Work
362+ rdf:about="">
363+ <dc:format>image/svg+xml</dc:format>
364+ <dc:type
365+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
366+ <dc:title></dc:title>
367+ </cc:Work>
368+ </rdf:RDF>
369+ </metadata>
370+ <g
371+ inkscape:label="BACKGROUND"
372+ inkscape:groupmode="layer"
373+ id="layer1"
374+ transform="translate(268,-635.29076)"
375+ style="display:inline">
376+ <path
377+ style="fill:url(#linearGradient6461);fill-opacity:1;stroke:none;display:inline;filter:url(#filter1121)"
378+ d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 C -264.11215,731.29077 -268,727.39888 -268,700.15563 Z"
379+ id="path6455"
380+ inkscape:connector-curvature="0"
381+ sodipodi:nodetypes="sssssssss" />
382+ </g>
383+ <g
384+ inkscape:groupmode="layer"
385+ id="layer3"
386+ inkscape:label="PLACE YOUR PICTOGRAM HERE"
387+ style="display:inline" />
388+ <g
389+ inkscape:groupmode="layer"
390+ id="layer2"
391+ inkscape:label="BADGE"
392+ style="display:none"
393+ sodipodi:insensitive="true">
394+ <g
395+ style="display:inline"
396+ transform="translate(-340.00001,-581)"
397+ id="g4394"
398+ clip-path="none">
399+ <g
400+ id="g855">
401+ <g
402+ inkscape:groupmode="maskhelper"
403+ id="g870"
404+ clip-path="url(#clipPath873)"
405+ style="opacity:0.6;filter:url(#filter891)">
406+ <path
407+ transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
408+ d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z"
409+ sodipodi:ry="12"
410+ sodipodi:rx="12"
411+ sodipodi:cy="552.36218"
412+ sodipodi:cx="252"
413+ id="path844"
414+ style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
415+ sodipodi:type="arc" />
416+ </g>
417+ <g
418+ id="g862">
419+ <path
420+ sodipodi:type="arc"
421+ style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
422+ id="path4398"
423+ sodipodi:cx="252"
424+ sodipodi:cy="552.36218"
425+ sodipodi:rx="12"
426+ sodipodi:ry="12"
427+ d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z"
428+ transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" />
429+ <path
430+ transform="matrix(1.25,0,0,1.25,33,-100.45273)"
431+ d="m 264,552.36218 a 12,12 0 1 1 -24,0 A 12,12 0 1 1 264,552.36218 Z"
432+ sodipodi:ry="12"
433+ sodipodi:rx="12"
434+ sodipodi:cy="552.36218"
435+ sodipodi:cx="252"
436+ id="path4400"
437+ style="color:#000000;fill:#dd4814;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
438+ sodipodi:type="arc" />
439+ <path
440+ sodipodi:type="star"
441+ style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
442+ id="path4459"
443+ sodipodi:sides="5"
444+ sodipodi:cx="666.19574"
445+ sodipodi:cy="589.50385"
446+ sodipodi:r1="7.2431178"
447+ sodipodi:r2="4.3458705"
448+ sodipodi:arg1="1.0471976"
449+ sodipodi:arg2="1.6755161"
450+ inkscape:flatsided="false"
451+ inkscape:rounded="0.1"
452+ inkscape:randomized="0"
453+ d="m 669.8173,595.77657 c -0.39132,0.22593 -3.62645,-1.90343 -4.07583,-1.95066 -0.44938,-0.0472 -4.05653,1.36297 -4.39232,1.06062 -0.3358,-0.30235 0.68963,-4.03715 0.59569,-4.47913 -0.0939,-0.44198 -2.5498,-3.43681 -2.36602,-3.8496 0.18379,-0.41279 4.05267,-0.59166 4.44398,-0.81759 0.39132,-0.22593 2.48067,-3.48704 2.93005,-3.4398 0.44938,0.0472 1.81505,3.67147 2.15084,3.97382 0.3358,0.30236 4.08294,1.2817 4.17689,1.72369 0.0939,0.44198 -2.9309,2.86076 -3.11469,3.27355 C 669.9821,591.68426 670.20862,595.55064 669.8173,595.77657 Z"
454+ transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
455+ </g>
456+ </g>
457+ </g>
458+ </g>
459+</svg>
460diff --git a/layer.yaml b/layer.yaml
461index ddd95fc..52f61b2 100644
462--- a/layer.yaml
463+++ b/layer.yaml
464@@ -1,5 +1,10 @@
465-includes: ['layer:basic', 'interface:http', 'layer:snap']
466-repo: 'https://git.launchpad.net/prometheus-blackbox-exporter-charm'
467+includes:
468+ - layer:basic
469+ - interface:http
470+ - layer:snap
471+ - interface:nrpe-external-master
472+ - interface:grafana-dashboard
473+repo: https://git.launchpad.net/prometheus-blackbox-exporter-charm
474 ignore: ['.*.swp' ]
475 options:
476 basic:
477diff --git a/metadata.yaml b/metadata.yaml
478index 831b6aa..6a9436b 100644
479--- a/metadata.yaml
480+++ b/metadata.yaml
481@@ -14,3 +14,14 @@ subordinate: false
482 provides:
483 blackbox-exporter:
484 interface: http
485+ nrpe-external-master:
486+ interface: nrpe-external-master
487+ scope: container
488+ dashboards:
489+ interface: grafana-dashboard
490+resources:
491+ dashboards:
492+ type: file
493+ filename: grafana-dashboards.zip
494+ description:
495+ Zip file to provide grafana dashboards
496diff --git a/reactive/prometheus-blackbox-exporter.py b/reactive/prometheus_blackbox_exporter.py
497similarity index 60%
498rename from reactive/prometheus-blackbox-exporter.py
499rename to reactive/prometheus_blackbox_exporter.py
500index 2da0bed..eb4677d 100644
501--- a/reactive/prometheus-blackbox-exporter.py
502+++ b/reactive/prometheus_blackbox_exporter.py
503@@ -1,18 +1,30 @@
504-import yaml
505+#!/usr/bin/python3
506+"""Installs and configures prometheus-blackbox-exporter."""
507+
508+import os
509+from pathlib import Path
510+import shutil
511+from zipfile import BadZipFile, ZipFile
512
513-from charmhelpers.core import host, hookenv
514+from charmhelpers.contrib.charmsupport import nrpe
515+from charmhelpers.core import hookenv, host
516 from charmhelpers.core.templating import render
517+from charms.layer import snap
518 from charms.reactive import (
519+ endpoint_from_flag,
520 endpoint_from_name,
521+ hook,
522 remove_state,
523 set_state,
524 when,
525+ when_all,
526 when_not,
527 )
528 from charms.reactive.helpers import any_file_changed, data_changed
529-from charms.layer import snap
530+import yaml
531
532
533+DASHBOARD_PATH = os.getcwd() + '/templates/'
534 SNAP_NAME = 'prometheus-blackbox-exporter'
535 SVC_NAME = 'snap.prometheus-blackbox-exporter.daemon'
536 PORT_DEF = 9115
537@@ -21,11 +33,13 @@ CONF_FILE_PATH = '/var/snap/prometheus-blackbox-exporter/current/blackbox.yml'
538
539
540 def templates_changed(tmpl_list):
541+ """Return list of changed files."""
542 return any_file_changed(['templates/{}'.format(x) for x in tmpl_list])
543
544
545 @when_not('blackbox-exporter.installed')
546 def install_packages():
547+ """Installs the snap exporter."""
548 hookenv.status_set('maintenance', 'Installing software')
549 config = hookenv.config()
550 channel = config.get('snap_channel', 'stable')
551@@ -34,11 +48,21 @@ def install_packages():
552 set_state('blackbox-exporter.do-check-reconfig')
553
554
555+@hook('upgrade-charm')
556+def upgrade():
557+ """Reset the install state on upgrade, to ensure resource extraction."""
558+ hookenv.status_set('maintenance', 'Charm upgrade in progress')
559+ update_dashboards_from_resource()
560+ set_state('blackbox-exporter.do-restart')
561+
562+
563 def get_modules():
564+ """Load the modules."""
565 config = hookenv.config()
566 try:
567 modules = yaml.safe_load(config.get('modules'))
568- except:
569+ except yaml.YAMLError as error:
570+ hookenv.log('Failed to load modules, error {}'.format(error))
571 return None
572
573 if 'modules' in modules:
574@@ -50,6 +74,7 @@ def get_modules():
575 @when('blackbox-exporter.installed')
576 @when('blackbox-exporter.do-reconfig-yaml')
577 def write_blackbox_exporter_config_yaml():
578+ """Render the template."""
579 modules = get_modules()
580 render(source=BLACKBOX_EXPORTER_YML_TMPL,
581 target=CONF_FILE_PATH,
582@@ -62,11 +87,13 @@ def write_blackbox_exporter_config_yaml():
583
584 @when('blackbox-exporter.started')
585 def check_config():
586+ """Check the config once started."""
587 set_state('blackbox-exporter.do-check-reconfig')
588
589
590 @when('blackbox-exporter.do-check-reconfig')
591 def check_reconfig_blackbox_exporter():
592+ """Configure the exporter."""
593 config = hookenv.config()
594
595 if data_changed('blackbox-exporter.config', config):
596@@ -80,6 +107,7 @@ def check_reconfig_blackbox_exporter():
597
598 @when('blackbox-exporter.do-restart')
599 def restart_blackbox_exporter():
600+ """Restart the exporter."""
601 if not host.service_running(SVC_NAME):
602 hookenv.log('Starting {}...'.format(SVC_NAME))
603 host.service_start(SVC_NAME)
604@@ -93,7 +121,98 @@ def restart_blackbox_exporter():
605
606 # Relations
607 @when('blackbox-exporter.started')
608-@when('blackbox-exporter.available') # Relation name is "blackbox-exporter"
609+@when('blackbox-exporter.available')
610 def configure_blackbox_exporter_relation():
611+ """Configure the http relation."""
612 target = endpoint_from_name('blackbox-exporter')
613 target.configure(PORT_DEF)
614+ remove_state('blackbox-exporter.configured')
615+
616+
617+@when('nrpe-external-master.changed')
618+def nrpe_changed():
619+ """Trigger nrpe update."""
620+ remove_state('blackbox-exporter.configured')
621+
622+
623+@when('blackbox-exporter.changed')
624+def prometheus_changed():
625+ """Trigger prometheus update."""
626+ remove_state('blackbox-exporter.configured')
627+
628+
629+@when('nrpe-external-master.available')
630+@when_not('blackbox-exporter.configured')
631+def update_nrpe_config(svc):
632+ """Configure the nrpe check for the service."""
633+ if not os.path.exists('/var/lib/nagios'):
634+ hookenv.status_set('blocked', 'Waiting for nrpe package installation')
635+ return
636+
637+ hookenv.status_set('maintenance', 'Configuring nrpe checks')
638+
639+ hostname = nrpe.get_nagios_hostname()
640+ nrpe_setup = nrpe.NRPE(hostname=hostname)
641+ nrpe_setup.add_check(shortname='prometheus_blackbox_exporter_http',
642+ check_cmd='check_http -I 127.0.0.1 -p {} -u /metrics'.format(PORT_DEF),
643+ description='Prometheus blackbox Exporter HTTP check')
644+ nrpe_setup.write()
645+ hookenv.status_set('active', 'Ready')
646+ set_state('blackbox-exporter.configured')
647+
648+
649+@when('blackbox-exporter.configured')
650+@when_not('nrpe-external-master.available')
651+def remove_nrpe_check():
652+ """Remove the nrpe check."""
653+ hostname = nrpe.get_nagios_hostname()
654+ nrpe_setup = nrpe.NRPE(hostname=hostname)
655+ nrpe_setup.remove_check(shortname="prometheus_blackbox_exporter_http")
656+ remove_state('blackbox-exporter.configured')
657+
658+
659+@when_all('endpoint.dashboards.joined')
660+def register_grafana_dashboards():
661+ """After joining to grafana, push the dashboard."""
662+ grafana_endpoint = endpoint_from_flag('endpoint.dashboards.joined')
663+
664+ if grafana_endpoint is None:
665+ return
666+
667+ hookenv.log('Grafana relation joined, push dashboard')
668+
669+ # load pre-distributed dashboards, that may havew been overwritten by resource
670+ dash_dir = Path(DASHBOARD_PATH)
671+ for dash_file in dash_dir.glob('*.json'):
672+ dashboard = dash_file.read_text()
673+ grafana_endpoint.register_dashboard(dash_file.stem, dashboard)
674+ hookenv.log('Pushed {}'.format(dash_file))
675+
676+
677+def update_dashboards_from_resource():
678+ """Extract resource zip file into templates directory."""
679+ dashboards_zip_resource = hookenv.resource_get('dashboards')
680+ if not dashboards_zip_resource:
681+ hookenv.log('No dashboards resource found', hookenv.DEBUG)
682+ # no dashboards zip found, go with the default distributed dashboard
683+ return
684+
685+ hookenv.log('Installing dashboards from resource', hookenv.DEBUG)
686+ try:
687+ shutil.copy(dashboards_zip_resource, DASHBOARD_PATH)
688+ except IOError as error:
689+ hookenv.log('Problem copying resource: {}'.format(error), hookenv.ERROR)
690+ return
691+
692+ try:
693+ with ZipFile(dashboards_zip_resource, 'r') as zipfile:
694+ zipfile.extractall(path=DASHBOARD_PATH)
695+ hookenv.log('Extracted dashboards from resource', hookenv.DEBUG)
696+ except BadZipFile as error:
697+ hookenv.log('BadZipFile: {}'.format(error), hookenv.ERROR)
698+ return
699+ except PermissionError as error:
700+ hookenv.log('Unable to unzip the provided resource: {}'.format(error), hookenv.ERROR)
701+ return
702+
703+ register_grafana_dashboards()
704diff --git a/requirements.txt b/requirements.txt
705new file mode 100644
706index 0000000..1713f68
707--- /dev/null
708+++ b/requirements.txt
709@@ -0,0 +1 @@
710+# Include python requirements here
711\ No newline at end of file
712diff --git a/tests/functional/requirements.txt b/tests/functional/requirements.txt
713new file mode 100644
714index 0000000..b7c9112
715--- /dev/null
716+++ b/tests/functional/requirements.txt
717@@ -0,0 +1 @@
718+git+https://github.com/openstack-charmers/zaza.git#egg=zaza
719diff --git a/tests/functional/tests/__init__.py b/tests/functional/tests/__init__.py
720new file mode 100644
721index 0000000..d236d73
722--- /dev/null
723+++ b/tests/functional/tests/__init__.py
724@@ -0,0 +1 @@
725+"""Zaza tests."""
726diff --git a/tests/functional/tests/bundles/bionic.yaml b/tests/functional/tests/bundles/bionic.yaml
727new file mode 100644
728index 0000000..4b510f1
729--- /dev/null
730+++ b/tests/functional/tests/bundles/bionic.yaml
731@@ -0,0 +1,16 @@
732+series: bionic
733+applications:
734+ prometheus-blackbox-exporter:
735+ options:
736+ snap_channel: stable
737+ num_units: 1
738+ nrpe:
739+ charm: cs:nrpe
740+ prometheus:
741+ charm: cs:prometheus2
742+ num_units: 1
743+relations:
744+ - - prometheus-blackbox-exporter:nrpe-external-master
745+ - nrpe:nrpe-external-master
746+ - - prometheus-blackbox-exporter:blackbox-exporter
747+ - prometheus:target
748\ No newline at end of file
749diff --git a/tests/functional/tests/bundles/focal.yaml b/tests/functional/tests/bundles/focal.yaml
750new file mode 100644
751index 0000000..2f3dc6c
752--- /dev/null
753+++ b/tests/functional/tests/bundles/focal.yaml
754@@ -0,0 +1,16 @@
755+series: focal
756+applications:
757+ prometheus-blackbox-exporter:
758+ options:
759+ snap_channel: stable
760+ num_units: 1
761+ nrpe:
762+ charm: cs:nrpe
763+ prometheus:
764+ charm: cs:prometheus2
765+ num_units: 1
766+relations:
767+ - - prometheus-blackbox-exporter:nrpe-external-master
768+ - nrpe:nrpe-external-master
769+ - - prometheus-blackbox-exporter:blackbox-exporter
770+ - prometheus:target
771\ No newline at end of file
772diff --git a/tests/functional/tests/bundles/overlays/local-charm-overlay.yaml.j2 b/tests/functional/tests/bundles/overlays/local-charm-overlay.yaml.j2
773new file mode 100644
774index 0000000..df81abd
775--- /dev/null
776+++ b/tests/functional/tests/bundles/overlays/local-charm-overlay.yaml.j2
777@@ -0,0 +1,3 @@
778+applications:
779+ prometheus-blackbox-exporter:
780+ charm: "{{ CHARM_BUILD_DIR }}/{{ charm_name }}"
781diff --git a/tests/functional/tests/bundles/xenial.yaml b/tests/functional/tests/bundles/xenial.yaml
782new file mode 100644
783index 0000000..be4f6dc
784--- /dev/null
785+++ b/tests/functional/tests/bundles/xenial.yaml
786@@ -0,0 +1,16 @@
787+series: xenial
788+applications:
789+ prometheus-blackbox-exporter:
790+ options:
791+ snap_channel: stable
792+ num_units: 1
793+ nrpe:
794+ charm: cs:nrpe
795+ prometheus:
796+ charm: cs:prometheus2
797+ num_units: 1
798+relations:
799+ - - prometheus-blackbox-exporter:nrpe-external-master
800+ - nrpe:nrpe-external-master
801+ - - prometheus-blackbox-exporter:blackbox-exporter
802+ - prometheus:target
803\ No newline at end of file
804diff --git a/tests/functional/tests/test_prometheus_blackbox_exporter.py b/tests/functional/tests/test_prometheus_blackbox_exporter.py
805new file mode 100644
806index 0000000..b02154b
807--- /dev/null
808+++ b/tests/functional/tests/test_prometheus_blackbox_exporter.py
809@@ -0,0 +1,78 @@
810+"""Encapsulate prometheus-blackbox-exporter testing."""
811+import logging
812+import time
813+import unittest
814+
815+import zaza.model as model
816+
817+
818+CURL_TIMEOUT = 180
819+REQ_TIMEOUT = 12
820+DEFAULT_API_PORT = "9115"
821+DEFAULT_API_URL = "/metrics"
822+
823+
824+class BasePrometheusBlackboxExporterTest(unittest.TestCase):
825+ """Base for Prometheus-blackbox-exporter charm tests."""
826+
827+ @classmethod
828+ def setUpClass(cls):
829+ """Set up tests."""
830+ super(BasePrometheusBlackboxExporterTest, cls).setUpClass()
831+ cls.model_name = model.get_juju_model()
832+ cls.application_name = "prometheus-blackbox-exporter"
833+ cls.lead_unit_name = model.get_lead_unit_name(
834+ cls.application_name, model_name=cls.model_name
835+ )
836+ cls.units = model.get_units(
837+ cls.application_name, model_name=cls.model_name
838+ )
839+ cls.prometheus_blackbox_exporter_ip = model.get_app_ips(cls.application_name)[0]
840+
841+
842+class CharmOperationTest(BasePrometheusBlackboxExporterTest):
843+ """Verify operations."""
844+
845+ def test_01_api_ready(self):
846+ """Verify if the API is ready.
847+
848+ Curl the api endpoint.
849+ We'll retry until the CURL_TIMEOUT.
850+ """
851+ curl_command = "curl http://localhost:{}/metrics".format(DEFAULT_API_PORT)
852+ timeout = time.time() + CURL_TIMEOUT
853+ while time.time() < timeout:
854+ response = model.run_on_unit(self.lead_unit_name, curl_command)
855+ if response["Code"] == "0":
856+ return
857+ logging.warning(
858+ "Unexpected curl response: {}. Retrying in 30s.".format(
859+ response
860+ )
861+ )
862+ time.sleep(30)
863+
864+ # we didn't get rc=0 in the allowed time, fail the test
865+ self.fail(
866+ "Prometheus-blackbox-exporter didn't respond to the command \n"
867+ "'{curl_command}' as expected.\n"
868+ "Result: {result}".format(
869+ curl_command=curl_command, result=response
870+ )
871+ )
872+
873+ def test_02_nrpe_http_check(self):
874+ """Verify nrpe check exists."""
875+ expected_nrpe_check = "command[check_prometheus_blackbox_exporter_http]={} -I 127.0.0.1 -p {} -u {}".format(
876+ "/usr/lib/nagios/plugins/check_http",
877+ DEFAULT_API_PORT,
878+ DEFAULT_API_URL
879+ )
880+ logging.debug('Verify the nrpe check is created and has the required content...')
881+ cmd = "cat /etc/nagios/nrpe.d/check_prometheus_blackbox_exporter_http.cfg"
882+ result = model.run_on_unit(self.lead_unit_name, cmd)
883+ code = result.get('Code')
884+ if code != '0':
885+ raise model.CommandRunFailed(cmd, result)
886+ content = result.get('Stdout')
887+ self.assertTrue(expected_nrpe_check in content)
888diff --git a/tests/functional/tests/tests.yaml b/tests/functional/tests/tests.yaml
889new file mode 100644
890index 0000000..dd32bc7
891--- /dev/null
892+++ b/tests/functional/tests/tests.yaml
893@@ -0,0 +1,11 @@
894+charm_name: prometheus-blackbox-exporter
895+gate_bundles:
896+ - focal
897+ - bionic
898+ - xenial
899+smoke_bundles:
900+ - bionic
901+dev_bundles:
902+ - bionic
903+tests:
904+ - tests.test_prometheus_blackbox_exporter.CharmOperationTest
905diff --git a/tox.ini b/tox.ini
906new file mode 100644
907index 0000000..1859695
908--- /dev/null
909+++ b/tox.ini
910@@ -0,0 +1,65 @@
911+[tox]
912+skipsdist=True
913+envlist = unit, functional
914+skip_missing_interpreters = True
915+
916+[testenv]
917+basepython = python3
918+setenv =
919+ PYTHONPATH = .
920+passenv =
921+ HOME
922+ CHARM_BUILD_DIR
923+ MODEL_SETTINGS
924+
925+[testenv:unit]
926+commands = pytest -v \
927+ --ignore {toxinidir}/tests/ \
928+ --ignore {toxinidir}/interfaces \
929+ --ignore {toxinidir}/layers \
930+ --cov=lib \
931+ --cov=actions \
932+ --cov-report=term \
933+ --cov-report=annotate:report/unit/coverage-annotated \
934+ --cov-report=html:report/unit/coverage-html \
935+ --html=report/unit/tests/index.html \
936+ --junitxml=report/unit/junit.xml
937+deps = -r{toxinidir}/tests/unit_tests/requirements.txt
938+ -r{toxinidir}/tests/requirements.txt
939+setenv = PYTHONPATH={toxinidir}/lib
940+
941+[testenv:func]
942+changedir = {toxinidir}/tests/functional
943+commands = functest-run-suite --keep-model
944+deps = -r{toxinidir}/tests/functional/requirements.txt
945+
946+[testenv:lint]
947+commands = flake8 --tee
948+deps =
949+ flake8
950+ flake8-colors
951+ flake8-docstrings
952+ flake8-import-order
953+ pep8-naming
954+
955+[flake8]
956+exclude =
957+ .git,
958+ __pycache__,
959+ .tox,
960+ layers,
961+ interfaces,
962+max-line-length = 120
963+max-complexity = 10
964+import-order-style = google
965+
966+[isort]
967+order_by_type = true
968+from_first = true
969+line_length = 120
970+
971+[pytest]
972+markers =
973+ deploy: mark deployment tests to allow running w/o redeploy
974+filterwarnings =
975+ ignore::DeprecationWarning

Subscribers

People subscribed via source and target branches

to all changes: