1{
2 system ? builtins.currentSystem,
3 config ? { },
4 pkgs ? import ../.. { inherit system config; },
5}:
6
7let
8 inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
9 inherit (pkgs.lib)
10 concatStringsSep
11 maintainers
12 mapAttrs
13 mkMerge
14 removeSuffix
15 replaceStrings
16 singleton
17 splitString
18 makeBinPath
19 ;
20
21 /*
22 * The attrset `exporterTests` contains one attribute
23 * for each exporter test. Each of these attributes
24 * is expected to be an attrset containing:
25 *
26 * `exporterConfig`:
27 * this attribute set contains config for the exporter itself
28 *
29 * `exporterTest`
30 * this attribute set contains test instructions
31 *
32 * `metricProvider` (optional)
33 * this attribute contains additional machine config
34 *
35 * `nodeName` (optional)
36 * override an incompatible testnode name
37 *
38 * Example:
39 * exporterTests.<exporterName> = {
40 * exporterConfig = {
41 * enable = true;
42 * };
43 * metricProvider = {
44 * services.<metricProvider>.enable = true;
45 * };
46 * exporterTest = ''
47 * wait_for_unit("prometheus-<exporterName>-exporter.service")
48 * wait_for_open_port(1234)
49 * succeed("curl -sSf 'localhost:1234/metrics'")
50 * '';
51 * };
52 *
53 * # this would generate the following test config:
54 *
55 * nodes.<exporterName> = {
56 * services.prometheus.<exporterName> = {
57 * enable = true;
58 * };
59 * services.<metricProvider>.enable = true;
60 * };
61 *
62 * testScript = ''
63 * <exporterName>.start()
64 * <exporterName>.wait_for_unit("prometheus-<exporterName>-exporter.service")
65 * <exporterName>.wait_for_open_port(1234)
66 * <exporterName>.succeed("curl -sSf 'localhost:1234/metrics'")
67 * <exporterName>.shutdown()
68 * '';
69 */
70
71 exporterTests = {
72 apcupsd = {
73 exporterConfig = {
74 enable = true;
75 };
76 metricProvider = {
77 services.apcupsd.enable = true;
78 };
79 exporterTest = ''
80 wait_for_unit("apcupsd.service")
81 wait_for_open_port(3551)
82 wait_for_unit("prometheus-apcupsd-exporter.service")
83 wait_for_open_port(9162)
84 succeed("curl -sSf http://localhost:9162/metrics | grep 'apcupsd_info'")
85 '';
86 };
87
88 artifactory = {
89 exporterConfig = {
90 enable = true;
91 artiUsername = "artifactory-username";
92 artiPassword = "artifactory-password";
93 };
94 exporterTest = ''
95 wait_for_unit("prometheus-artifactory-exporter.service")
96 wait_for_open_port(9531)
97 succeed(
98 "curl -sSf http://localhost:9531/metrics | grep 'artifactory_up'"
99 )
100 '';
101 };
102
103 bind = {
104 exporterConfig = {
105 enable = true;
106 };
107 metricProvider = {
108 services.bind.enable = true;
109 services.bind.extraConfig = ''
110 statistics-channels {
111 inet 127.0.0.1 port 8053 allow { localhost; };
112 };
113 '';
114 };
115 exporterTest = ''
116 wait_for_unit("prometheus-bind-exporter.service")
117 wait_for_open_port(9119)
118 succeed(
119 "curl -sSf http://localhost:9119/metrics | grep 'bind_query_recursions_total 0'"
120 )
121 '';
122 };
123
124 bird = {
125 exporterConfig = {
126 enable = true;
127 };
128 metricProvider = {
129 services.bird.enable = true;
130 services.bird.config = ''
131 router id 127.0.0.1;
132
133 protocol kernel MyObviousTestString {
134 ipv4 {
135 import all;
136 export none;
137 };
138 }
139
140 protocol device {
141 }
142 '';
143 };
144 exporterTest = ''
145 wait_for_unit("prometheus-bird-exporter.service")
146 wait_for_open_port(9324)
147 wait_until_succeeds(
148 "curl -sSf http://localhost:9324/metrics | grep 'MyObviousTestString'"
149 )
150 '';
151 };
152
153 bitcoin = {
154 exporterConfig = {
155 enable = true;
156 rpcUser = "bitcoinrpc";
157 rpcPasswordFile = pkgs.writeText "password" "hunter2";
158 };
159 metricProvider = {
160 services.bitcoind.default.enable = true;
161 services.bitcoind.default.rpc.users.bitcoinrpc.passwordHMAC =
162 "e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7";
163 };
164 exporterTest = ''
165 wait_for_unit("prometheus-bitcoin-exporter.service")
166 wait_for_unit("bitcoind-default.service")
167 wait_for_open_port(9332)
168 succeed("curl -sSf http://localhost:9332/metrics | grep '^bitcoin_blocks '")
169 '';
170 };
171
172 blackbox = {
173 exporterConfig = {
174 enable = true;
175 configFile = pkgs.writeText "config.yml" (
176 builtins.toJSON {
177 modules.icmp_v6 = {
178 prober = "icmp";
179 icmp.preferred_ip_protocol = "ip6";
180 };
181 }
182 );
183 };
184 exporterTest = ''
185 wait_for_unit("prometheus-blackbox-exporter.service")
186 wait_for_open_port(9115)
187 succeed(
188 "curl -sSf 'http://localhost:9115/probe?target=localhost&module=icmp_v6' | grep 'probe_success 1'"
189 )
190 '';
191 };
192
193 borgmatic = {
194 exporterConfig = {
195 enable = true;
196 user = "root";
197 };
198 metricProvider = {
199 services.borgmatic.enable = true;
200 services.borgmatic.settings.source_directories = [ "/home" ];
201 services.borgmatic.settings.repositories = [
202 {
203 label = "local";
204 path = "/var/backup";
205 }
206 ];
207 services.borgmatic.settings.keep_daily = 10;
208 };
209 exporterTest = ''
210 succeed("borgmatic rcreate -e none")
211 succeed("borgmatic")
212 wait_for_unit("prometheus-borgmatic-exporter.service")
213 wait_for_open_port(9996)
214 succeed("curl -sSf localhost:9996/metrics | grep 'borg_total_backups{repository=\"/var/backup\"} 1'")
215 '';
216 };
217
218 collectd = {
219 exporterConfig = {
220 enable = true;
221 extraFlags = [ "--web.collectd-push-path /collectd" ];
222 };
223 exporterTest =
224 let
225 postData = replaceStrings [ "\n" ] [ "" ] ''
226 [{
227 "values":[23],
228 "dstypes":["gauge"],
229 "type":"gauge",
230 "interval":1000,
231 "host":"testhost",
232 "plugin":"testplugin",
233 "time":DATE
234 }]
235 '';
236 in
237 ''
238 wait_for_unit("prometheus-collectd-exporter.service")
239 wait_for_open_port(9103)
240 succeed(
241 'echo \'${postData}\'> /tmp/data.json'
242 )
243 succeed('sed -i -e "s DATE $(date +%s) " /tmp/data.json')
244 succeed(
245 "curl -sSfH 'Content-Type: application/json' -X POST --data @/tmp/data.json localhost:9103/collectd"
246 )
247 succeed(
248 "curl -sSf localhost:9103/metrics | grep 'collectd_testplugin_gauge{instance=\"testhost\"} 23'"
249 )
250 '';
251 };
252
253 deluge = {
254 exporterConfig = {
255 enable = true;
256 port = 1234;
257 listenAddress = "127.0.0.1";
258
259 delugeUser = "user";
260 delugePort = 2345;
261 delugePasswordFile = pkgs.writeText "password" "weak_password";
262 };
263 metricProvider = {
264 services.deluge.enable = true;
265 services.deluge.declarative = true;
266 services.deluge.config.daemon_port = 2345;
267 services.deluge.authFile = pkgs.writeText "authFile" ''
268 localclient:abcdef:10
269 user:weak_password:10
270 '';
271 };
272 exporterTest = ''
273 wait_for_unit("deluged.service")
274 wait_for_open_port(2345)
275 wait_for_unit("prometheus-deluge-exporter.service")
276 wait_for_open_port(1234)
277 succeed("curl -sSf http://localhost:1234 | grep 'deluge_torrents'")
278 '';
279 };
280
281 dnsmasq = {
282 exporterConfig = {
283 enable = true;
284 leasesPath = "/var/lib/dnsmasq/dnsmasq.leases";
285 };
286 metricProvider = {
287 services.dnsmasq.enable = true;
288 };
289 exporterTest = ''
290 wait_for_unit("dnsmasq.service")
291 wait_for_open_port(53)
292 wait_for_file("/var/lib/dnsmasq/dnsmasq.leases")
293 wait_for_unit("prometheus-dnsmasq-exporter.service")
294 wait_for_open_port(9153)
295 succeed("curl -sSf http://localhost:9153/metrics | grep 'dnsmasq_leases 0'")
296 '';
297 };
298
299 dnssec = {
300 exporterConfig = {
301 enable = true;
302 configuration = {
303 records = [
304 {
305 zone = "example.com";
306 record = "@";
307 type = "SOA";
308 }
309 ];
310 };
311 resolvers = [ "127.0.0.1:53" ];
312 };
313 metricProvider = {
314 services.knot = {
315 enable = true;
316 settingsFile = pkgs.writeText "knot.conf" ''
317 server:
318 listen: 127.0.0.1@53
319 template:
320 - id: default
321 storage: ${
322 pkgs.buildEnv {
323 name = "zones";
324 paths = [
325 (pkgs.writeTextDir "example.com.zone" ''
326 @ SOA ns1.example.com. noc.example.com. 2024032401 86400 7200 3600000 172800
327 @ NS ns1
328 ns1 A 192.168.0.1
329 '')
330 ];
331 }
332 }
333 zonefile-load: difference
334 zonefile-sync: -1
335 zone:
336 - domain: example.com
337 file: example.com.zone
338 dnssec-signing: on
339 '';
340 };
341 };
342 exporterTest = ''
343 wait_for_unit("knot.service")
344 wait_for_open_port(53)
345 wait_for_unit("prometheus-dnssec-exporter.service")
346 wait_for_open_port(9204)
347 succeed("curl -sSf http://localhost:9204/metrics | grep 'example.com'")
348 '';
349 };
350
351 # Access to WHOIS server is required to properly test this exporter, so
352 # just perform basic sanity check that the exporter is running and returns
353 # a failure.
354 domain = {
355 exporterConfig = {
356 enable = true;
357 };
358 exporterTest = ''
359 wait_for_unit("prometheus-domain-exporter.service")
360 wait_for_open_port(9222)
361 succeed("curl -sSf 'http://localhost:9222/probe?target=nixos.org'")
362 '';
363 };
364
365 dovecot = {
366 exporterConfig = {
367 enable = true;
368 scopes = [ "global" ];
369 socketPath = "/var/run/dovecot2/old-stats";
370 user = "root"; # <- don't use user root in production
371 };
372 metricProvider = {
373 services.dovecot2.enable = true;
374 };
375 exporterTest = ''
376 wait_for_unit("prometheus-dovecot-exporter.service")
377 wait_for_open_port(9166)
378 succeed(
379 "curl -sSf http://localhost:9166/metrics | grep 'dovecot_up{scope=\"global\"} 1'"
380 )
381 '';
382 };
383
384 exportarr-sonarr =
385 let
386 apikey = "eccff6a992bc2e4b88e46d064b26bb4e";
387 in
388 {
389 nodeName = "exportarr_sonarr";
390 exporterConfig = {
391 enable = true;
392 url = "http://127.0.0.1:8989";
393 apiKeyFile = pkgs.writeText "dummy-api-key" apikey;
394 };
395 metricProvider = {
396 services.sonarr = {
397 enable = true;
398 environmentFiles = [ (pkgs.writeText "sonarr-env" "SONARR__AUTH__APIKEY=${apikey}") ];
399 };
400 };
401 exporterTest = ''
402 wait_for_unit("sonarr.service")
403 wait_for_open_port(8989)
404 wait_for_unit("prometheus-exportarr-sonarr-exporter.service")
405 wait_for_open_port(9708)
406 succeed("curl -sSf http://localhost:9708/metrics | grep sonarr_series_total")
407 '';
408 };
409
410 ebpf = {
411 exporterConfig = {
412 enable = true;
413 names = [ "timers" ];
414 };
415 exporterTest = ''
416 wait_for_unit("prometheus-ebpf-exporter.service")
417 wait_for_open_port(9435)
418 succeed(
419 "curl -sSf http://localhost:9435/metrics | grep 'ebpf_exporter_enabled_configs{name=\"timers\"} 1'"
420 )
421 '';
422 };
423
424 fastly = {
425 exporterConfig = {
426 enable = true;
427 environmentFile = pkgs.writeText "fastly-exporter-env" "FASTLY_API_TOKEN=abc123";
428 };
429
430 exporterTest = ''
431 wait_for_unit("prometheus-fastly-exporter.service")
432 wait_for_open_port(9118)
433 '';
434 };
435
436 fritzbox = {
437 # TODO add proper test case
438 exporterConfig = {
439 enable = true;
440 };
441 exporterTest = ''
442 wait_for_unit("prometheus-fritzbox-exporter.service")
443 wait_for_open_port(9133)
444 succeed(
445 "curl -sSf http://localhost:9133/metrics | grep 'fritzbox_exporter_collect_errors 0'"
446 )
447 '';
448 };
449
450 graphite = {
451 exporterConfig = {
452 enable = true;
453 port = 9108;
454 graphitePort = 9109;
455 mappingSettings.mappings = [
456 {
457 match = "test.*.*";
458 name = "testing";
459 labels = {
460 protocol = "$1";
461 author = "$2";
462 };
463 }
464 ];
465 };
466 exporterTest = ''
467 wait_for_unit("prometheus-graphite-exporter.service")
468 wait_for_open_port(9108)
469 wait_for_open_port(9109)
470 succeed("echo test.tcp.foo-bar 1234 $(date +%s) | nc -w1 localhost 9109")
471 succeed("curl -sSf http://localhost:9108/metrics | grep 'testing{author=\"foo-bar\",protocol=\"tcp\"} 1234'")
472 '';
473 };
474
475 idrac = {
476 exporterConfig = {
477 enable = true;
478 port = 9348;
479 configuration = {
480 hosts = {
481 default = {
482 username = "username";
483 password = "password";
484 };
485 };
486 };
487 };
488 exporterTest = ''
489 wait_for_unit("prometheus-idrac-exporter.service")
490 wait_for_open_port(9348)
491 wait_until_succeeds("curl localhost:9348")
492 '';
493 };
494
495 influxdb = {
496 exporterConfig = {
497 enable = true;
498 sampleExpiry = "3s";
499 };
500 exporterTest = ''
501 wait_for_unit("prometheus-influxdb-exporter.service")
502 wait_for_open_port(9122)
503 succeed(
504 "curl -XPOST http://localhost:9122/write --data-binary 'influxdb_exporter,distro=nixos,added_in=21.09 value=1'"
505 )
506 succeed(
507 "curl -sSf http://localhost:9122/metrics | grep 'nixos'"
508 )
509 execute("sleep 5")
510 fail(
511 "curl -sSf http://localhost:9122/metrics | grep 'nixos'"
512 )
513 '';
514 };
515
516 ipmi = {
517 exporterConfig = {
518 enable = true;
519 };
520 exporterTest = ''
521 wait_for_unit("prometheus-ipmi-exporter.service")
522 wait_for_open_port(9290)
523 succeed(
524 "curl -sSf http://localhost:9290/metrics | grep 'ipmi_scrape_duration_seconds'"
525 )
526 '';
527 };
528
529 jitsi = {
530 exporterConfig = {
531 enable = true;
532 };
533 metricProvider = {
534 systemd.services.prometheus-jitsi-exporter.after = [ "jitsi-videobridge2.service" ];
535 services.jitsi-videobridge = {
536 enable = true;
537 colibriRestApi = true;
538 };
539 };
540 exporterTest = ''
541 wait_for_unit("jitsi-videobridge2.service")
542 wait_for_open_port(8080)
543 wait_for_unit("prometheus-jitsi-exporter.service")
544 wait_for_open_port(9700)
545 wait_until_succeeds(
546 'journalctl -eu prometheus-jitsi-exporter.service -o cat | grep "key=participants"'
547 )
548 succeed("curl -sSf 'localhost:9700/metrics' | grep 'jitsi_participants 0'")
549 '';
550 };
551
552 json = {
553 exporterConfig = {
554 enable = true;
555 configFile = pkgs.writeText "json-exporter-conf.json" (
556 builtins.toJSON {
557 modules = {
558 default = {
559 metrics = [
560 {
561 name = "json_test_metric";
562 path = "{ .test }";
563 }
564 ];
565 };
566 };
567 }
568 );
569 };
570 metricProvider = {
571 systemd.services.prometheus-json-exporter.after = [ "nginx.service" ];
572 services.nginx = {
573 enable = true;
574 virtualHosts.localhost.locations."/".extraConfig = ''
575 return 200 "{\"test\":1}";
576 '';
577 };
578 };
579 exporterTest = ''
580 wait_for_unit("nginx.service")
581 wait_for_open_port(80)
582 wait_for_unit("prometheus-json-exporter.service")
583 wait_for_open_port(7979)
584 succeed(
585 "curl -sSf 'localhost:7979/probe?target=http://localhost' | grep 'json_test_metric 1'"
586 )
587 '';
588 };
589
590 knot = {
591 exporterConfig = {
592 enable = true;
593 };
594 metricProvider = {
595 services.knot = {
596 enable = true;
597 extraArgs = [ "-v" ];
598 settingsFile = pkgs.writeText "knot.conf" ''
599 server:
600 listen: 127.0.0.1@53
601
602 template:
603 - id: default
604 global-module: mod-stats
605 dnssec-signing: off
606 zonefile-sync: -1
607 zonefile-load: difference
608 storage: ${
609 pkgs.buildEnv {
610 name = "foo";
611 paths = [
612 (pkgs.writeTextDir "test.zone" ''
613 @ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
614 @ NS ns1
615 @ NS ns2
616 ns1 A 192.168.0.1
617 '')
618 ];
619 }
620 }
621
622 mod-stats:
623 - id: custom
624 edns-presence: on
625 query-type: on
626
627 zone:
628 - domain: test
629 file: test.zone
630 module: mod-stats/custom
631 '';
632 };
633 };
634 exporterTest = ''
635 wait_for_unit("knot.service")
636 wait_for_unit("prometheus-knot-exporter.service")
637 wait_for_open_port(9433)
638 succeed("curl -sSf 'localhost:9433' | grep '2\.019031301'")
639 '';
640 };
641
642 keylight = {
643 # A hardware device is required to properly test this exporter, so just
644 # perform a couple of basic sanity checks that the exporter is running
645 # and requires a target, but cannot reach a specified target.
646 exporterConfig = {
647 enable = true;
648 };
649 exporterTest = ''
650 wait_for_unit("prometheus-keylight-exporter.service")
651 wait_for_open_port(9288)
652 succeed(
653 "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics | grep '400'"
654 )
655 succeed(
656 "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics?target=nosuchdevice | grep '500'"
657 )
658 '';
659 };
660
661 lnd = {
662 exporterConfig = {
663 enable = true;
664 lndTlsPath = "/var/lib/lnd/tls.cert";
665 lndMacaroonDir = "/var/lib/lnd";
666 extraFlags = [ "--lnd.network=regtest" ];
667 };
668 metricProvider = {
669 systemd.services.prometheus-lnd-exporter.serviceConfig.RestartSec = 15;
670 systemd.services.prometheus-lnd-exporter.after = [ "lnd.service" ];
671 services.bitcoind.regtest = {
672 enable = true;
673 extraConfig = ''
674 rpcauth=bitcoinrpc:e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7
675 zmqpubrawblock=tcp://127.0.0.1:28332
676 zmqpubrawtx=tcp://127.0.0.1:28333
677 # https://github.com/lightningnetwork/lnd/issues/9163
678 deprecatedrpc=warnings
679 '';
680 extraCmdlineOptions = [ "-regtest" ];
681 };
682 systemd.services.lnd = {
683 serviceConfig.ExecStart = ''
684 ${pkgs.lnd}/bin/lnd \
685 --datadir=/var/lib/lnd \
686 --tlscertpath=/var/lib/lnd/tls.cert \
687 --tlskeypath=/var/lib/lnd/tls.key \
688 --logdir=/var/log/lnd \
689 --bitcoin.active \
690 --bitcoin.regtest \
691 --bitcoin.node=bitcoind \
692 --bitcoind.rpcuser=bitcoinrpc \
693 --bitcoind.rpcpass=hunter2 \
694 --bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332 \
695 --bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333 \
696 --readonlymacaroonpath=/var/lib/lnd/readonly.macaroon
697 '';
698 serviceConfig.StateDirectory = "lnd";
699 wantedBy = [ "multi-user.target" ];
700 after = [ "network.target" ];
701 };
702 # initialize wallet, creates macaroon needed by exporter
703 systemd.services.lnd.postStart = ''
704 ${pkgs.curl}/bin/curl \
705 --retry 20 \
706 --retry-delay 1 \
707 --retry-connrefused \
708 --cacert /var/lib/lnd/tls.cert \
709 -X GET \
710 https://localhost:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /tmp/seed
711 ${pkgs.curl}/bin/curl \
712 --retry 20 \
713 --retry-delay 1 \
714 --retry-connrefused \
715 --cacert /var/lib/lnd/tls.cert \
716 -X POST \
717 -d "{\"wallet_password\": \"asdfasdfasdf\", \"cipher_seed_mnemonic\": $(cat /tmp/seed | tr -d '\n')}" \
718 https://localhost:8080/v1/initwallet
719 '';
720 };
721 exporterTest = ''
722 wait_for_unit("lnd.service")
723 wait_for_open_port(10009)
724 wait_for_unit("prometheus-lnd-exporter.service")
725 wait_for_open_port(9092)
726 succeed("curl -sSf localhost:9092/metrics | grep '^lnd_peer_count'")
727 '';
728 };
729
730 mail = {
731 exporterConfig = {
732 enable = true;
733 configuration = {
734 monitoringInterval = "2s";
735 mailCheckTimeout = "10s";
736 servers = [
737 {
738 name = "testserver";
739 server = "localhost";
740 port = 25;
741 from = "mail-exporter@localhost";
742 to = "mail-exporter@localhost";
743 detectionDir = "/var/spool/mail/mail-exporter/new";
744 }
745 ];
746 };
747 };
748 metricProvider = {
749 services.postfix.enable = true;
750 systemd.services.prometheus-mail-exporter = {
751 after = [ "postfix.service" ];
752 requires = [ "postfix.service" ];
753 serviceConfig = {
754 ExecStartPre = [
755 "${pkgs.writeShellScript "create-maildir" ''
756 mkdir -p -m 0700 mail-exporter/new
757 ''}"
758 ];
759 ProtectHome = true;
760 ReadOnlyPaths = "/";
761 ReadWritePaths = "/var/spool/mail";
762 WorkingDirectory = "/var/spool/mail";
763 };
764 };
765 users.users.mailexporter = {
766 isSystemUser = true;
767 group = "mailexporter";
768 };
769 users.groups.mailexporter = { };
770 };
771 exporterTest = ''
772 wait_for_unit("postfix.service")
773 wait_for_unit("prometheus-mail-exporter.service")
774 wait_for_open_port(9225)
775 wait_until_succeeds(
776 "curl -sSf http://localhost:9225/metrics | grep 'mail_deliver_success{configname=\"testserver\"} 1'"
777 )
778 '';
779 };
780
781 mikrotik = {
782 exporterConfig = {
783 enable = true;
784 extraFlags = [ "-timeout=1s" ];
785 configuration = {
786 devices = [
787 {
788 name = "router";
789 address = "192.168.42.48";
790 user = "prometheus";
791 password = "shh";
792 }
793 ];
794 features = {
795 bgp = true;
796 dhcp = true;
797 dhcpl = true;
798 dhcpv6 = true;
799 health = true;
800 routes = true;
801 poe = true;
802 pools = true;
803 optics = true;
804 w60g = true;
805 wlansta = true;
806 wlanif = true;
807 monitor = true;
808 ipsec = true;
809 };
810 };
811 };
812 exporterTest = ''
813 wait_for_unit("prometheus-mikrotik-exporter.service")
814 wait_for_open_port(9436)
815 succeed(
816 "curl -sSf http://localhost:9436/metrics | grep 'mikrotik_scrape_collector_success{device=\"router\"} 0'"
817 )
818 '';
819 };
820
821 modemmanager = {
822 exporterConfig = {
823 enable = true;
824 refreshRate = "10s";
825 };
826 metricProvider = {
827 # ModemManager is installed when NetworkManager is enabled. Ensure it is
828 # started and is wanted by NM and the exporter to start everything up
829 # in the right order.
830 networking.networkmanager.enable = true;
831 systemd.services.ModemManager = {
832 enable = true;
833 wantedBy = [
834 "NetworkManager.service"
835 "prometheus-modemmanager-exporter.service"
836 ];
837 };
838 };
839 exporterTest = ''
840 wait_for_unit("ModemManager.service")
841 wait_for_unit("prometheus-modemmanager-exporter.service")
842 wait_for_open_port(9539)
843 succeed(
844 "curl -sSf http://localhost:9539/metrics | grep 'modemmanager_info'"
845 )
846 '';
847 };
848
849 mqtt = {
850 exporterConfig = {
851 enable = true;
852 environmentFile = pkgs.writeText "mqtt-exporter-envfile" ''
853 MQTT_PASSWORD=testpassword
854 '';
855 };
856 metricProvider = {
857 services.mosquitto = {
858 enable = true;
859 listeners = [
860 {
861 users.exporter = {
862 acl = [ "read #" ];
863 passwordFile = pkgs.writeText "mosquitto-password" "testpassword";
864 };
865 }
866 ];
867 };
868 systemd.services.prometheus-mqtt-exporter = {
869 wants = [ "mosquitto.service" ];
870 after = [ "mosquitto.service" ];
871 };
872 };
873 exporterTest = ''
874 wait_for_unit("mosquitto.service")
875 wait_for_unit("prometheus-mqtt-exporter.service")
876 wait_for_open_port(9000)
877 succeed(
878 "curl -sSf http://localhost:9000/metrics | grep '^python_info'"
879 )
880 '';
881 };
882
883 mysqld = {
884 exporterConfig = {
885 enable = true;
886 runAsLocalSuperUser = true;
887 configFile = pkgs.writeText "test-prometheus-exporter-mysqld-config.my-cnf" ''
888 [client]
889 user = exporter
890 password = snakeoilpassword
891 '';
892 };
893 metricProvider = {
894 services.mysql = {
895 enable = true;
896 package = pkgs.mariadb;
897 initialScript = pkgs.writeText "mysql-init-script.sql" ''
898 CREATE USER 'exporter'@'localhost'
899 IDENTIFIED BY 'snakeoilpassword'
900 WITH MAX_USER_CONNECTIONS 3;
901 GRANT PROCESS, REPLICATION CLIENT, SLAVE MONITOR, SELECT ON *.* TO 'exporter'@'localhost';
902 '';
903 };
904 };
905 exporterTest = ''
906 wait_for_unit("prometheus-mysqld-exporter.service")
907 wait_for_open_port(9104)
908 wait_for_unit("mysql.service")
909 succeed("curl -sSf http://localhost:9104/metrics | grep 'mysql_up 1'")
910 systemctl("stop mysql.service")
911 succeed("curl -sSf http://localhost:9104/metrics | grep 'mysql_up 0'")
912 systemctl("start mysql.service")
913 wait_for_unit("mysql.service")
914 succeed("curl -sSf http://localhost:9104/metrics | grep 'mysql_up 1'")
915 '';
916 };
917
918 nextcloud = {
919 exporterConfig = {
920 enable = true;
921 passwordFile = "/var/nextcloud-pwfile";
922 url = "http://localhost";
923 };
924 metricProvider = {
925 systemd.services.nc-pwfile =
926 let
927 passfile = (pkgs.writeText "pwfile" "snakeoilpw");
928 in
929 {
930 requiredBy = [ "prometheus-nextcloud-exporter.service" ];
931 before = [ "prometheus-nextcloud-exporter.service" ];
932 serviceConfig.ExecStart = ''
933 ${pkgs.coreutils}/bin/install -o nextcloud-exporter -m 0400 ${passfile} /var/nextcloud-pwfile
934 '';
935 };
936 services.nginx = {
937 enable = true;
938 virtualHosts."localhost" = {
939 basicAuth.nextcloud-exporter = "snakeoilpw";
940 locations."/" = {
941 root = "${pkgs.prometheus-nextcloud-exporter.src}/serverinfo/testdata";
942 tryFiles = "/negative-space.json =404";
943 };
944 };
945 };
946 };
947 exporterTest = ''
948 wait_for_unit("nginx.service")
949 wait_for_unit("prometheus-nextcloud-exporter.service")
950 wait_for_open_port(9205)
951 succeed("curl -sSf http://localhost:9205/metrics | grep 'nextcloud_up 1'")
952 '';
953 };
954
955 nginx = {
956 exporterConfig = {
957 enable = true;
958 constLabels = [ "foo=bar" ];
959 };
960 metricProvider = {
961 services.nginx = {
962 enable = true;
963 statusPage = true;
964 virtualHosts."test".extraConfig = "return 204;";
965 };
966 };
967 exporterTest = ''
968 wait_for_unit("nginx.service")
969 wait_for_unit("prometheus-nginx-exporter.service")
970 wait_for_open_port(9113)
971 succeed("curl -sSf http://localhost:9113/metrics | grep 'nginx_up{foo=\"bar\"} 1'")
972 '';
973 };
974
975 nginxlog = {
976 exporterConfig = {
977 enable = true;
978 group = "nginx";
979 settings = {
980 namespaces = [
981 {
982 name = "filelogger";
983 source = {
984 files = [ "/var/log/nginx/filelogger.access.log" ];
985 };
986 }
987 {
988 name = "syslogger";
989 source = {
990 syslog = {
991 listen_address = "udp://127.0.0.1:10000";
992 format = "rfc3164";
993 tags = [ "nginx" ];
994 };
995 };
996 }
997 ];
998 };
999 };
1000 metricProvider = {
1001 services.nginx = {
1002 enable = true;
1003 httpConfig = ''
1004 server {
1005 listen 80;
1006 server_name filelogger.local;
1007 access_log /var/log/nginx/filelogger.access.log;
1008 }
1009 server {
1010 listen 81;
1011 server_name syslogger.local;
1012 access_log syslog:server=127.0.0.1:10000,tag=nginx,severity=info;
1013 }
1014 '';
1015 };
1016 };
1017 exporterTest = ''
1018 wait_for_unit("nginx.service")
1019 wait_for_unit("prometheus-nginxlog-exporter.service")
1020 wait_for_open_port(9117)
1021 wait_for_open_port(80)
1022 wait_for_open_port(81)
1023 succeed("curl http://localhost")
1024 execute("sleep 1")
1025 succeed(
1026 "curl -sSf http://localhost:9117/metrics | grep 'filelogger_http_response_count_total' | grep 1"
1027 )
1028 succeed("curl http://localhost:81")
1029 execute("sleep 1")
1030 succeed(
1031 "curl -sSf http://localhost:9117/metrics | grep 'syslogger_http_response_count_total' | grep 1"
1032 )
1033 '';
1034 };
1035
1036 node = {
1037 exporterConfig = {
1038 enable = true;
1039 };
1040 exporterTest = ''
1041 wait_for_unit("prometheus-node-exporter.service")
1042 wait_for_open_port(9100)
1043 succeed(
1044 "curl -sSf http://localhost:9100/metrics | grep 'node_exporter_build_info{.\\+} 1'"
1045 )
1046 '';
1047 };
1048
1049 node-cert = {
1050 nodeName = "node_cert";
1051 exporterConfig = {
1052 enable = true;
1053 paths = [ "/run/certs" ];
1054 };
1055 exporterTest = ''
1056 wait_for_unit("prometheus-node-cert-exporter.service")
1057 wait_for_open_port(9141)
1058 wait_until_succeeds(
1059 "curl -sSf http://localhost:9141/metrics | grep 'ssl_certificate_expiry_seconds{.\\+path=\"/run/certs/node-cert\\.cert\".\\+}'"
1060 )
1061 '';
1062
1063 metricProvider = {
1064 system.activationScripts.cert.text = ''
1065 mkdir -p /run/certs
1066 cd /run/certs
1067
1068 cat >ca.template <<EOF
1069 organization = "prometheus-node-cert-exporter"
1070 cn = "prometheus-node-cert-exporter"
1071 expiration_days = 365
1072 ca
1073 cert_signing_key
1074 crl_signing_key
1075 EOF
1076
1077 ${pkgs.gnutls}/bin/certtool \
1078 --generate-privkey \
1079 --key-type rsa \
1080 --sec-param High \
1081 --outfile node-cert.key
1082
1083 ${pkgs.gnutls}/bin/certtool \
1084 --generate-self-signed \
1085 --load-privkey node-cert.key \
1086 --template ca.template \
1087 --outfile node-cert.cert
1088 '';
1089 };
1090 };
1091
1092 pgbouncer = {
1093 exporterConfig = {
1094 enable = true;
1095 connectionEnvFile = "${pkgs.writeText "connstr-env" ''
1096 PGBOUNCER_EXPORTER_CONNECTION_STRING=postgres://admin@localhost:6432/pgbouncer?sslmode=disable
1097 ''}";
1098 };
1099
1100 metricProvider = {
1101 services.postgresql.enable = true;
1102 services.pgbouncer = {
1103 enable = true;
1104 settings = {
1105 pgbouncer = {
1106 listen_addr = "*";
1107 auth_type = "any";
1108 max_client_conn = 99;
1109 # https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration
1110 ignore_startup_parameters = "extra_float_digits";
1111 };
1112 databases = {
1113 postgres = concatStringsSep " " [
1114 "host=/run/postgresql"
1115 "port=5432"
1116 "auth_user=postgres"
1117 "dbname=postgres"
1118 ];
1119 };
1120 };
1121 };
1122 };
1123 exporterTest = ''
1124 wait_for_unit("postgresql.service")
1125 wait_for_unit("pgbouncer.service")
1126 wait_for_unit("prometheus-pgbouncer-exporter.service")
1127 wait_for_open_port(9127)
1128 succeed("curl -sSf http://localhost:9127/metrics | grep 'pgbouncer_up 1'")
1129 succeed(
1130 "curl -sSf http://localhost:9127/metrics | grep 'pgbouncer_config_max_client_connections 99'"
1131 )
1132 '';
1133 };
1134
1135 php-fpm = {
1136 nodeName = "php_fpm";
1137 exporterConfig = {
1138 enable = true;
1139 environmentFile = pkgs.writeTextFile {
1140 name = "/tmp/prometheus-php-fpm-exporter.env";
1141 text = ''
1142 PHP_FPM_SCRAPE_URI="tcp://127.0.0.1:9000/status"
1143 '';
1144 };
1145 };
1146 metricProvider = {
1147 users.users."php-fpm-exporter" = {
1148 isSystemUser = true;
1149 group = "php-fpm-exporter";
1150 };
1151 users.groups."php-fpm-exporter" = { };
1152 services.phpfpm.pools."php-fpm-exporter" = {
1153 user = "php-fpm-exporter";
1154 group = "php-fpm-exporter";
1155 settings = {
1156 "pm" = "dynamic";
1157 "pm.max_children" = 32;
1158 "pm.max_requests" = 500;
1159 "pm.start_servers" = 2;
1160 "pm.min_spare_servers" = 2;
1161 "pm.max_spare_servers" = 5;
1162 "pm.status_path" = "/status";
1163 "listen" = "127.0.0.1:9000";
1164 "listen.allowed_clients" = "127.0.0.1";
1165 };
1166 phpEnv."PATH" = makeBinPath [ pkgs.php ];
1167 };
1168 };
1169 exporterTest = ''
1170 wait_for_unit("phpfpm-php-fpm-exporter.service")
1171 wait_for_unit("prometheus-php-fpm-exporter.service")
1172 succeed("curl -sSf http://localhost:9253/metrics | grep 'phpfpm_up{.*} 1'")
1173 '';
1174 };
1175
1176 ping = {
1177 exporterConfig = {
1178 enable = true;
1179
1180 settings = {
1181 targets = [
1182 {
1183 "localhost" = {
1184 alias = "local machine";
1185 env = "prod";
1186 type = "domain";
1187 };
1188 }
1189 {
1190 "127.0.0.1" = {
1191 alias = "local machine";
1192 type = "v4";
1193 };
1194 }
1195 {
1196 "::1" = {
1197 alias = "local machine";
1198 type = "v6";
1199 };
1200 }
1201 {
1202 "google.com" = { };
1203 }
1204 ];
1205 dns = { };
1206 ping = {
1207 interval = "2s";
1208 timeout = "3s";
1209 history-size = 42;
1210 payload-size = 56;
1211 };
1212 log = {
1213 level = "warn";
1214 };
1215 };
1216 };
1217
1218 exporterTest = ''
1219 wait_for_unit("prometheus-ping-exporter.service")
1220 wait_for_open_port(9427)
1221 succeed("curl -sSf http://localhost:9427/metrics | grep 'ping_up{.*} 1'")
1222 '';
1223 };
1224
1225 postfix = {
1226 exporterConfig = {
1227 enable = true;
1228 };
1229 metricProvider = {
1230 services.postfix.enable = true;
1231 };
1232 exporterTest = ''
1233 wait_for_unit("prometheus-postfix-exporter.service")
1234 wait_for_file("/var/lib/postfix/queue/public/showq")
1235 wait_for_open_port(9154)
1236 wait_until_succeeds(
1237 "curl -sSf http://localhost:9154/metrics | grep 'postfix_up{path=\"/var/lib/postfix/queue/public/showq\"} 1'"
1238 )
1239 succeed(
1240 "curl -sSf http://localhost:9154/metrics | grep 'postfix_smtpd_connects_total 0'"
1241 )
1242 succeed("curl -sSf http://localhost:9154/metrics | grep 'postfix_up{.*} 1'")
1243 '';
1244 };
1245
1246 postgres = {
1247 exporterConfig = {
1248 enable = true;
1249 runAsLocalSuperUser = true;
1250 };
1251 metricProvider = {
1252 services.postgresql.enable = true;
1253 };
1254 exporterTest = ''
1255 wait_for_unit("prometheus-postgres-exporter.service")
1256 wait_for_open_port(9187)
1257 wait_for_unit("postgresql.service")
1258 succeed(
1259 "curl -sSf http://localhost:9187/metrics | grep 'pg_exporter_last_scrape_error 0'"
1260 )
1261 succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 1'")
1262 systemctl("stop postgresql.service")
1263 succeed(
1264 "curl -sSf http://localhost:9187/metrics | grep -v 'pg_exporter_last_scrape_error 0'"
1265 )
1266 succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 0'")
1267 systemctl("start postgresql.service")
1268 wait_for_unit("postgresql.service")
1269 succeed(
1270 "curl -sSf http://localhost:9187/metrics | grep 'pg_exporter_last_scrape_error 0'"
1271 )
1272 succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 1'")
1273 '';
1274 };
1275
1276 process = {
1277 exporterConfig = {
1278 enable = true;
1279 settings.process_names = [
1280 # Remove nix store path from process name
1281 {
1282 name = "{{.Matches.Wrapped}} {{ .Matches.Args }}";
1283 cmdline = [ "^/nix/store[^ ]*/(?P<Wrapped>[^ /]*) (?P<Args>.*)" ];
1284 }
1285 ];
1286 };
1287 exporterTest = ''
1288 wait_for_unit("prometheus-process-exporter.service")
1289 wait_for_open_port(9256)
1290 wait_until_succeeds(
1291 "curl -sSf localhost:9256/metrics | grep -q '{}'".format(
1292 'namedprocess_namegroup_cpu_seconds_total{groupname="process-exporter '
1293 )
1294 )
1295 '';
1296 };
1297
1298 pve =
1299 let
1300 pveExporterEnvFile = pkgs.writeTextFile {
1301 name = "pve.env";
1302 text = ''
1303 PVE_USER="test_user@pam"
1304 PVE_PASSWORD="hunter3"
1305 PVE_VERIFY_SSL="false"
1306 '';
1307 };
1308 in
1309 {
1310 exporterConfig = {
1311 enable = true;
1312 environmentFile = pveExporterEnvFile;
1313 };
1314 exporterTest = ''
1315 wait_for_unit("prometheus-pve-exporter.service")
1316 wait_for_open_port(9221)
1317 wait_until_succeeds("curl localhost:9221")
1318 '';
1319 };
1320
1321 py-air-control = {
1322 nodeName = "py_air_control";
1323 exporterConfig = {
1324 enable = true;
1325 deviceHostname = "127.0.0.1";
1326 };
1327 exporterTest = ''
1328 wait_for_unit("prometheus-py-air-control-exporter.service")
1329 wait_for_open_port(9896)
1330 succeed(
1331 "curl -sSf http://localhost:9896/metrics | grep 'py_air_control_sampling_error_total'"
1332 )
1333 '';
1334 };
1335
1336 redis = {
1337 exporterConfig = {
1338 enable = true;
1339 };
1340 metricProvider.services.redis.servers."".enable = true;
1341 exporterTest = ''
1342 wait_for_unit("redis.service")
1343 wait_for_unit("prometheus-redis-exporter.service")
1344 wait_for_open_port(6379)
1345 wait_for_open_port(9121)
1346 wait_until_succeeds("curl -sSf localhost:9121/metrics | grep 'redis_up 1'")
1347 '';
1348 };
1349
1350 restic =
1351 let
1352 repository = "rest:http://127.0.0.1:8000";
1353 passwordFile = pkgs.writeText "restic-test-password" "test-password";
1354 in
1355 {
1356 exporterConfig = {
1357 enable = true;
1358 inherit repository passwordFile;
1359 };
1360 metricProvider = {
1361 services.restic.server = {
1362 enable = true;
1363 extraFlags = [ "--no-auth" ];
1364 };
1365 environment.systemPackages = [ pkgs.restic ];
1366 };
1367 exporterTest = ''
1368 # prometheus-restic-exporter.service fails without initialised repository
1369 systemctl("stop prometheus-restic-exporter.service")
1370
1371 # Initialise the repository
1372 wait_for_unit("restic-rest-server.service")
1373 wait_for_open_port(8000)
1374 succeed("restic init --repo ${repository} --password-file ${passwordFile}")
1375
1376 systemctl("start prometheus-restic-exporter.service")
1377 wait_for_unit("prometheus-restic-exporter.service")
1378 wait_for_open_port(9753)
1379 wait_until_succeeds("curl -sSf localhost:9753/metrics | grep 'restic_check_success 1.0'")
1380 '';
1381 };
1382
1383 rspamd = {
1384 exporterConfig = {
1385 enable = true;
1386 };
1387 metricProvider = {
1388 services.rspamd.enable = true;
1389 };
1390 exporterTest = ''
1391 wait_for_unit("rspamd.service")
1392 wait_for_unit("prometheus-rspamd-exporter.service")
1393 wait_for_open_port(11334)
1394 wait_for_open_port(7980)
1395 wait_until_succeeds(
1396 "curl -sSf 'localhost:7980/probe?target=http://localhost:11334/stat' | grep 'rspamd_scanned{host=\"rspamd\"} 0'"
1397 )
1398 '';
1399 };
1400
1401 rtl_433 = {
1402 exporterConfig = {
1403 enable = true;
1404 };
1405 metricProvider = {
1406 # Mock rtl_433 binary to return a dummy metric stream.
1407 nixpkgs.overlays = [
1408 (self: super: {
1409 rtl_433 = self.runCommand "rtl_433" { } ''
1410 mkdir -p "$out/bin"
1411 cat <<EOF > "$out/bin/rtl_433"
1412 #!/bin/sh
1413 while true; do
1414 printf '{"time" : "2020-04-26 13:37:42", "model" : "zopieux", "id" : 55, "channel" : 3, "temperature_C" : 18.000}\n'
1415 sleep 4
1416 done
1417 EOF
1418 chmod +x "$out/bin/rtl_433"
1419 '';
1420 })
1421 ];
1422 };
1423 exporterTest = ''
1424 wait_for_unit("prometheus-rtl_433-exporter.service")
1425 wait_for_open_port(9550)
1426 wait_until_succeeds(
1427 "curl -sSf localhost:9550/metrics | grep '{}'".format(
1428 'rtl_433_temperature_celsius{channel="3",id="55",location="",model="zopieux"} 18'
1429 )
1430 )
1431 '';
1432 };
1433
1434 sabnzbd = {
1435 exporterConfig = {
1436 enable = true;
1437 servers = [
1438 {
1439 baseUrl = "http://localhost:8080";
1440 apiKeyFile = "/var/sabnzbd-apikey";
1441 }
1442 ];
1443 };
1444
1445 metricProvider = {
1446 services.sabnzbd.enable = true;
1447
1448 # unrar is required for sabnzbd
1449 nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (pkgs.lib.getName pkg) [ "unrar" ];
1450
1451 # extract the generated api key before starting
1452 systemd.services.sabnzbd-apikey = {
1453 requires = [ "sabnzbd.service" ];
1454 after = [ "sabnzbd.service" ];
1455 requiredBy = [ "prometheus-sabnzbd-exporter.service" ];
1456 before = [ "prometheus-sabnzbd-exporter.service" ];
1457 script = ''
1458 grep -Po '^api_key = \K.+' /var/lib/sabnzbd/sabnzbd.ini > /var/sabnzbd-apikey
1459 '';
1460 };
1461 };
1462
1463 exporterTest = ''
1464 wait_for_unit("sabnzbd.service")
1465 wait_for_unit("prometheus-sabnzbd-exporter.service")
1466 wait_for_open_port(8080)
1467 wait_for_open_port(9387)
1468 wait_until_succeeds(
1469 "curl -sSf 'localhost:9387/metrics' | grep 'sabnzbd_queue_size{sabnzbd_instance=\"http://localhost:8080\"} 0.0'"
1470 )
1471 '';
1472 };
1473
1474 scaphandre = {
1475 exporterConfig = {
1476 enable = true;
1477 };
1478 metricProvider = {
1479 boot.kernelModules = [ "intel_rapl_common" ];
1480 };
1481 exporterTest = ''
1482 wait_for_unit("prometheus-scaphandre-exporter.service")
1483 wait_for_open_port(8080)
1484 wait_until_succeeds(
1485 "curl -sSf 'localhost:8080/metrics'"
1486 )
1487 '';
1488 };
1489
1490 shelly = {
1491 exporterConfig = {
1492 enable = true;
1493 metrics-file = "${pkgs.writeText "test.json" ''{}''}";
1494 };
1495 exporterTest = ''
1496 wait_for_unit("prometheus-shelly-exporter.service")
1497 wait_for_open_port(9784)
1498 wait_until_succeeds(
1499 "curl -sSf 'localhost:9784/metrics'"
1500 )
1501 '';
1502 };
1503
1504 script = {
1505 exporterConfig = {
1506 enable = true;
1507 settings.scripts = [
1508 {
1509 name = "success";
1510 script = "sleep 1";
1511 }
1512 ];
1513 };
1514 exporterTest = ''
1515 wait_for_unit("prometheus-script-exporter.service")
1516 wait_for_open_port(9172)
1517 wait_until_succeeds(
1518 "curl -sSf 'localhost:9172/probe?name=success' | grep -q '{}'".format(
1519 'script_success{script="success"} 1'
1520 )
1521 )
1522 '';
1523 };
1524
1525 smartctl = {
1526 exporterConfig = {
1527 enable = true;
1528 devices = [
1529 "/dev/vda"
1530 ];
1531 };
1532 exporterTest = ''
1533 wait_until_succeeds(
1534 'journalctl -eu prometheus-smartctl-exporter.service -o cat | grep "Unable to detect device type"'
1535 )
1536 '';
1537 };
1538
1539 smokeping = {
1540 exporterConfig = {
1541 enable = true;
1542 hosts = [ "127.0.0.1" ];
1543 };
1544 exporterTest = ''
1545 wait_for_unit("prometheus-smokeping-exporter.service")
1546 wait_for_open_port(9374)
1547 wait_until_succeeds(
1548 "curl -sSf localhost:9374/metrics | grep '{}' | grep -v ' 0$'".format(
1549 'smokeping_requests_total{host="127.0.0.1",ip="127.0.0.1",source=""} '
1550 )
1551 )
1552 wait_until_succeeds(
1553 "curl -sSf localhost:9374/metrics | grep '{}'".format(
1554 'smokeping_response_ttl{host="127.0.0.1",ip="127.0.0.1",source=""}'
1555 )
1556 )
1557 '';
1558 };
1559
1560 snmp = {
1561 exporterConfig = {
1562 enable = true;
1563 configuration = {
1564 auths.public_v2 = {
1565 community = "public";
1566 version = 2;
1567 };
1568 };
1569 };
1570 exporterTest = ''
1571 wait_for_unit("prometheus-snmp-exporter.service")
1572 wait_for_open_port(9116)
1573 succeed("curl -sSf localhost:9116/metrics | grep 'snmp_request_errors_total 0'")
1574 '';
1575 };
1576
1577 sql = {
1578 exporterConfig = {
1579 configuration.jobs.points = {
1580 interval = "1m";
1581 connections = [
1582 "postgres://prometheus-sql-exporter@/data?host=/run/postgresql&sslmode=disable"
1583 ];
1584 queries = {
1585 points = {
1586 labels = [ "name" ];
1587 help = "Amount of points accumulated per person";
1588 values = [ "amount" ];
1589 query = "SELECT SUM(amount) as amount, name FROM points GROUP BY name";
1590 };
1591 };
1592 };
1593 enable = true;
1594 user = "prometheus-sql-exporter";
1595 };
1596 metricProvider = {
1597 services.postgresql = {
1598 enable = true;
1599 initialScript = builtins.toFile "init.sql" ''
1600 CREATE DATABASE data;
1601 \c data;
1602 CREATE TABLE points (amount INT, name TEXT);
1603 INSERT INTO points(amount, name) VALUES (1, 'jack');
1604 INSERT INTO points(amount, name) VALUES (2, 'jill');
1605 INSERT INTO points(amount, name) VALUES (3, 'jack');
1606
1607 CREATE USER "prometheus-sql-exporter";
1608 GRANT ALL PRIVILEGES ON DATABASE data TO "prometheus-sql-exporter";
1609 GRANT SELECT ON points TO "prometheus-sql-exporter";
1610 '';
1611 };
1612 systemd.services.prometheus-sql-exporter.after = [ "postgresql.service" ];
1613 };
1614 exporterTest = ''
1615 wait_for_unit("prometheus-sql-exporter.service")
1616 wait_for_open_port(9237)
1617 succeed("curl http://localhost:9237/metrics | grep -c 'sql_points{' | grep 2")
1618 '';
1619 };
1620
1621 statsd = {
1622 exporterConfig = {
1623 enable = true;
1624 };
1625 exporterTest = ''
1626 wait_for_unit("prometheus-statsd-exporter.service")
1627 wait_for_open_port(9102)
1628 succeed("curl http://localhost:9102/metrics | grep 'statsd_exporter_build_info{'")
1629 wait_until_succeeds(
1630 "echo 'test.udp:1|c' > /dev/udp/localhost/9125 && \
1631 curl http://localhost:9102/metrics | grep 'test_udp 1'",
1632 timeout=10
1633 )
1634 wait_until_succeeds(
1635 "echo 'test.tcp:1|c' > /dev/tcp/localhost/9125 && \
1636 curl http://localhost:9102/metrics | grep 'test_tcp 1'",
1637 timeout=10
1638 )
1639 '';
1640 };
1641
1642 surfboard = {
1643 exporterConfig = {
1644 enable = true;
1645 modemAddress = "localhost";
1646 };
1647 metricProvider = {
1648 systemd.services.prometheus-surfboard-exporter.after = [ "nginx.service" ];
1649 services.nginx = {
1650 enable = true;
1651 virtualHosts.localhost.locations."/cgi-bin/status".extraConfig = ''
1652 return 204;
1653 '';
1654 };
1655 };
1656 exporterTest = ''
1657 wait_for_unit("nginx.service")
1658 wait_for_open_port(80)
1659 wait_for_unit("prometheus-surfboard-exporter.service")
1660 wait_for_open_port(9239)
1661 succeed("curl -sSf localhost:9239/metrics | grep 'surfboard_up 1'")
1662 '';
1663 };
1664
1665 systemd = {
1666 exporterConfig = {
1667 enable = true;
1668
1669 extraFlags = [
1670 "--systemd.collector.enable-restart-count"
1671 ];
1672 };
1673 metricProvider = { };
1674 exporterTest = ''
1675 wait_for_unit("prometheus-systemd-exporter.service")
1676 wait_for_open_port(9558)
1677 wait_until_succeeds(
1678 "curl -sSf localhost:9558/metrics | grep '{}'".format(
1679 'systemd_unit_state{name="basic.target",state="active",type="target"} 1'
1680 )
1681 )
1682 succeed(
1683 "curl -sSf localhost:9558/metrics | grep '{}'".format(
1684 'systemd_service_restart_total{name="prometheus-systemd-exporter.service"} 0'
1685 )
1686 )
1687 '';
1688 };
1689
1690 unpoller = {
1691 nodeName = "unpoller";
1692 exporterConfig.enable = true;
1693 exporterConfig.controllers = [ { } ];
1694 exporterTest = ''
1695 wait_until_succeeds(
1696 'journalctl -eu prometheus-unpoller-exporter.service -o cat | grep "Connection Error"'
1697 )
1698 '';
1699 };
1700
1701 unbound = {
1702 exporterConfig = {
1703 enable = true;
1704 unbound.host = "unix:///run/unbound/unbound.ctl";
1705 };
1706 metricProvider = {
1707 services.unbound = {
1708 enable = true;
1709 localControlSocketPath = "/run/unbound/unbound.ctl";
1710 };
1711 systemd.services.prometheus-unbound-exporter.serviceConfig = {
1712 SupplementaryGroups = [ "unbound" ];
1713 };
1714 };
1715 exporterTest = ''
1716 wait_for_unit("unbound.service")
1717 wait_for_unit("prometheus-unbound-exporter.service")
1718 wait_for_open_port(9167)
1719 wait_until_succeeds("curl -sSf localhost:9167/metrics | grep 'unbound_up 1'")
1720 '';
1721 };
1722
1723 v2ray = {
1724 exporterConfig = {
1725 enable = true;
1726 };
1727
1728 metricProvider = {
1729 systemd.services.prometheus-nginx-exporter.after = [ "v2ray.service" ];
1730 services.v2ray = {
1731 enable = true;
1732 config = {
1733 stats = { };
1734 api = {
1735 tag = "api";
1736 services = [ "StatsService" ];
1737 };
1738 inbounds = [
1739 {
1740 port = 1080;
1741 listen = "127.0.0.1";
1742 protocol = "http";
1743 }
1744 {
1745 listen = "127.0.0.1";
1746 port = 54321;
1747 protocol = "dokodemo-door";
1748 settings = {
1749 address = "127.0.0.1";
1750 };
1751 tag = "api";
1752 }
1753 ];
1754 outbounds = [
1755 {
1756 protocol = "freedom";
1757 }
1758 {
1759 protocol = "freedom";
1760 settings = { };
1761 tag = "api";
1762 }
1763 ];
1764 routing = {
1765 strategy = "rules";
1766 settings = {
1767 rules = [
1768 {
1769 inboundTag = [ "api" ];
1770 outboundTag = "api";
1771 type = "field";
1772 }
1773 ];
1774 };
1775 };
1776 };
1777 };
1778 };
1779 exporterTest = ''
1780 wait_for_unit("prometheus-v2ray-exporter.service")
1781 wait_for_open_port(9299)
1782 succeed("curl -sSf localhost:9299/scrape | grep 'v2ray_up 1'")
1783 '';
1784 };
1785
1786 varnish = {
1787 exporterConfig = {
1788 enable = true;
1789 instance = "/run/varnish/varnish";
1790 group = "varnish";
1791 };
1792 metricProvider = {
1793 systemd.services.prometheus-varnish-exporter.after = [
1794 "varnish.service"
1795 ];
1796 services.varnish = {
1797 enable = true;
1798 stateDir = "/run/varnish/varnish";
1799 config = ''
1800 vcl 4.0;
1801 backend default {
1802 .host = "127.0.0.1";
1803 .port = "80";
1804 }
1805 '';
1806 };
1807 };
1808 exporterTest = ''
1809 wait_for_unit("prometheus-varnish-exporter.service")
1810 wait_for_open_port(6081)
1811 wait_for_open_port(9131)
1812 succeed("curl -sSf http://localhost:9131/metrics | grep 'varnish_up 1'")
1813 '';
1814 };
1815
1816 wireguard =
1817 let
1818 snakeoil = import ./wireguard/snakeoil-keys.nix;
1819 publicKeyWithoutNewlines = replaceStrings [ "\n" ] [ "" ] snakeoil.peer1.publicKey;
1820 in
1821 {
1822 exporterConfig.enable = true;
1823 metricProvider = {
1824 networking.wireguard.interfaces.wg0 = {
1825 ips = [
1826 "10.23.42.1/32"
1827 "fc00::1/128"
1828 ];
1829 listenPort = 23542;
1830
1831 inherit (snakeoil.peer0) privateKey;
1832
1833 peers = singleton {
1834 allowedIPs = [
1835 "10.23.42.2/32"
1836 "fc00::2/128"
1837 ];
1838
1839 inherit (snakeoil.peer1) publicKey;
1840 };
1841 };
1842 systemd.services.prometheus-wireguard-exporter.after = [ "wireguard-wg0.service" ];
1843 };
1844 exporterTest = ''
1845 wait_for_unit("prometheus-wireguard-exporter.service")
1846 wait_for_open_port(9586)
1847 wait_until_succeeds(
1848 "curl -sSf http://localhost:9586/metrics | grep '${publicKeyWithoutNewlines}'"
1849 )
1850 '';
1851 };
1852
1853 zfs = {
1854 exporterConfig = {
1855 enable = true;
1856 };
1857 metricProvider = {
1858 boot.supportedFilesystems = [ "zfs" ];
1859 networking.hostId = "7327ded7";
1860 };
1861 exporterTest = ''
1862 wait_for_unit("prometheus-zfs-exporter.service")
1863 wait_for_unit("zfs.target")
1864 wait_for_open_port(9134)
1865 wait_until_succeeds("curl -f localhost:9134/metrics | grep 'zfs_scrape_collector_success{.*} 1'")
1866 '';
1867 };
1868 };
1869in
1870mapAttrs (
1871 exporter: testConfig:
1872 (makeTest (
1873 let
1874 nodeName = testConfig.nodeName or exporter;
1875
1876 in
1877 {
1878 name = "prometheus-${exporter}-exporter";
1879
1880 nodes.${nodeName} = mkMerge [
1881 {
1882 services.prometheus.exporters.${exporter} = testConfig.exporterConfig;
1883 }
1884 testConfig.metricProvider or { }
1885 ];
1886
1887 testScript = ''
1888 ${nodeName}.start()
1889 ${concatStringsSep "\n" (
1890 map (
1891 line:
1892 if
1893 builtins.any (b: b) [
1894 (builtins.match "^[[:space:]]*$" line != null)
1895 (builtins.substring 0 1 line == "#")
1896 (builtins.substring 0 1 line == " ")
1897 (builtins.substring 0 1 line == ")")
1898 ]
1899 then
1900 line
1901 else
1902 "${nodeName}.${line}"
1903 ) (splitString "\n" (removeSuffix "\n" testConfig.exporterTest))
1904 )}
1905 ${nodeName}.shutdown()
1906 '';
1907
1908 meta = with maintainers; {
1909 maintainers = [ willibutz ];
1910 };
1911 }
1912 ))
1913) exporterTests