1{ system ? builtins.currentSystem
2, config ? { }
3, pkgs ? import ../.. { inherit system config; }
4}:
5
6let
7 inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
8 inherit (pkgs.lib) concatStringsSep maintainers mapAttrs mkMerge
9 removeSuffix replaceChars singleton splitString;
10
11 /*
12 * The attrset `exporterTests` contains one attribute
13 * for each exporter test. Each of these attributes
14 * is expected to be an attrset containing:
15 *
16 * `exporterConfig`:
17 * this attribute set contains config for the exporter itself
18 *
19 * `exporterTest`
20 * this attribute set contains test instructions
21 *
22 * `metricProvider` (optional)
23 * this attribute contains additional machine config
24 *
25 * `nodeName` (optional)
26 * override an incompatible testnode name
27 *
28 * Example:
29 * exporterTests.<exporterName> = {
30 * exporterConfig = {
31 * enable = true;
32 * };
33 * metricProvider = {
34 * services.<metricProvider>.enable = true;
35 * };
36 * exporterTest = ''
37 * wait_for_unit("prometheus-<exporterName>-exporter.service")
38 * wait_for_open_port("1234")
39 * succeed("curl -sSf 'localhost:1234/metrics'")
40 * '';
41 * };
42 *
43 * # this would generate the following test config:
44 *
45 * nodes.<exporterName> = {
46 * services.prometheus.<exporterName> = {
47 * enable = true;
48 * };
49 * services.<metricProvider>.enable = true;
50 * };
51 *
52 * testScript = ''
53 * <exporterName>.start()
54 * <exporterName>.wait_for_unit("prometheus-<exporterName>-exporter.service")
55 * <exporterName>.wait_for_open_port("1234")
56 * <exporterName>.succeed("curl -sSf 'localhost:1234/metrics'")
57 * <exporterName>.shutdown()
58 * '';
59 */
60
61 exporterTests = {
62 apcupsd = {
63 exporterConfig = {
64 enable = true;
65 };
66 metricProvider = {
67 services.apcupsd.enable = true;
68 };
69 exporterTest = ''
70 wait_for_unit("apcupsd.service")
71 wait_for_open_port(3551)
72 wait_for_unit("prometheus-apcupsd-exporter.service")
73 wait_for_open_port(9162)
74 succeed("curl -sSf http://localhost:9162/metrics | grep 'apcupsd_info'")
75 '';
76 };
77
78 artifactory = {
79 exporterConfig = {
80 enable = true;
81 artiUsername = "artifactory-username";
82 artiPassword = "artifactory-password";
83 };
84 exporterTest = ''
85 wait_for_unit("prometheus-artifactory-exporter.service")
86 wait_for_open_port(9531)
87 succeed(
88 "curl -sSf http://localhost:9531/metrics | grep 'artifactory_up'"
89 )
90 '';
91 };
92
93 bind = {
94 exporterConfig = {
95 enable = true;
96 };
97 metricProvider = {
98 services.bind.enable = true;
99 services.bind.extraConfig = ''
100 statistics-channels {
101 inet 127.0.0.1 port 8053 allow { localhost; };
102 };
103 '';
104 };
105 exporterTest = ''
106 wait_for_unit("prometheus-bind-exporter.service")
107 wait_for_open_port(9119)
108 succeed(
109 "curl -sSf http://localhost:9119/metrics | grep 'bind_query_recursions_total 0'"
110 )
111 '';
112 };
113
114 bird = {
115 exporterConfig = {
116 enable = true;
117 };
118 metricProvider = {
119 services.bird2.enable = true;
120 services.bird2.config = ''
121 router id 127.0.0.1;
122
123 protocol kernel MyObviousTestString {
124 ipv4 {
125 import all;
126 export none;
127 };
128 }
129
130 protocol device {
131 }
132 '';
133 };
134 exporterTest = ''
135 wait_for_unit("prometheus-bird-exporter.service")
136 wait_for_open_port(9324)
137 wait_until_succeeds(
138 "curl -sSf http://localhost:9324/metrics | grep 'MyObviousTestString'"
139 )
140 '';
141 };
142
143 bitcoin = {
144 exporterConfig = {
145 enable = true;
146 rpcUser = "bitcoinrpc";
147 rpcPasswordFile = pkgs.writeText "password" "hunter2";
148 };
149 metricProvider = {
150 services.bitcoind.default.enable = true;
151 services.bitcoind.default.rpc.users.bitcoinrpc.passwordHMAC = "e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7";
152 };
153 exporterTest = ''
154 wait_for_unit("prometheus-bitcoin-exporter.service")
155 wait_for_unit("bitcoind-default.service")
156 wait_for_open_port(9332)
157 succeed("curl -sSf http://localhost:9332/metrics | grep '^bitcoin_blocks '")
158 '';
159 };
160
161 blackbox = {
162 exporterConfig = {
163 enable = true;
164 configFile = pkgs.writeText "config.yml" (builtins.toJSON {
165 modules.icmp_v6 = {
166 prober = "icmp";
167 icmp.preferred_ip_protocol = "ip6";
168 };
169 });
170 };
171 exporterTest = ''
172 wait_for_unit("prometheus-blackbox-exporter.service")
173 wait_for_open_port(9115)
174 succeed(
175 "curl -sSf 'http://localhost:9115/probe?target=localhost&module=icmp_v6' | grep 'probe_success 1'"
176 )
177 '';
178 };
179
180 collectd = {
181 exporterConfig = {
182 enable = true;
183 extraFlags = [ "--web.collectd-push-path /collectd" ];
184 };
185 exporterTest = let postData = replaceChars [ "\n" ] [ "" ] ''
186 [{
187 "values":[23],
188 "dstypes":["gauge"],
189 "type":"gauge",
190 "interval":1000,
191 "host":"testhost",
192 "plugin":"testplugin",
193 "time":DATE
194 }]
195 ''; in
196 ''
197 wait_for_unit("prometheus-collectd-exporter.service")
198 wait_for_open_port(9103)
199 succeed(
200 'echo \'${postData}\'> /tmp/data.json'
201 )
202 succeed('sed -ie "s DATE $(date +%s) " /tmp/data.json')
203 succeed(
204 "curl -sSfH 'Content-Type: application/json' -X POST --data @/tmp/data.json localhost:9103/collectd"
205 )
206 succeed(
207 "curl -sSf localhost:9103/metrics | grep 'collectd_testplugin_gauge{instance=\"testhost\"} 23'"
208 )
209 '';
210 };
211
212 dnsmasq = {
213 exporterConfig = {
214 enable = true;
215 leasesPath = "/var/lib/dnsmasq/dnsmasq.leases";
216 };
217 metricProvider = {
218 services.dnsmasq.enable = true;
219 };
220 exporterTest = ''
221 wait_for_unit("prometheus-dnsmasq-exporter.service")
222 wait_for_open_port(9153)
223 succeed("curl -sSf http://localhost:9153/metrics | grep 'dnsmasq_leases 0'")
224 '';
225 };
226
227 # Access to WHOIS server is required to properly test this exporter, so
228 # just perform basic sanity check that the exporter is running and returns
229 # a failure.
230 domain = {
231 exporterConfig = {
232 enable = true;
233 };
234 exporterTest = ''
235 wait_for_unit("prometheus-domain-exporter.service")
236 wait_for_open_port(9222)
237 succeed(
238 "curl -sSf 'http://localhost:9222/probe?target=nixos.org' | grep 'domain_probe_success 0'"
239 )
240 '';
241 };
242
243 dovecot = {
244 exporterConfig = {
245 enable = true;
246 scopes = [ "global" ];
247 socketPath = "/var/run/dovecot2/old-stats";
248 user = "root"; # <- don't use user root in production
249 };
250 metricProvider = {
251 services.dovecot2.enable = true;
252 };
253 exporterTest = ''
254 wait_for_unit("prometheus-dovecot-exporter.service")
255 wait_for_open_port(9166)
256 succeed(
257 "curl -sSf http://localhost:9166/metrics | grep 'dovecot_up{scope=\"global\"} 1'"
258 )
259 '';
260 };
261
262 fritzbox = {
263 # TODO add proper test case
264 exporterConfig = {
265 enable = true;
266 };
267 exporterTest = ''
268 wait_for_unit("prometheus-fritzbox-exporter.service")
269 wait_for_open_port(9133)
270 succeed(
271 "curl -sSf http://localhost:9133/metrics | grep 'fritzbox_exporter_collect_errors 0'"
272 )
273 '';
274 };
275
276 influxdb = {
277 exporterConfig = {
278 enable = true;
279 sampleExpiry = "3s";
280 };
281 exporterTest = ''
282 wait_for_unit("prometheus-influxdb-exporter.service")
283 wait_for_open_port(9122)
284 succeed(
285 "curl -XPOST http://localhost:9122/write --data-binary 'influxdb_exporter,distro=nixos,added_in=21.09 value=1'"
286 )
287 succeed(
288 "curl -sSf http://localhost:9122/metrics | grep 'nixos'"
289 )
290 execute("sleep 5")
291 fail(
292 "curl -sSf http://localhost:9122/metrics | grep 'nixos'"
293 )
294 '';
295 };
296
297 jitsi = {
298 exporterConfig = {
299 enable = true;
300 };
301 metricProvider = {
302 systemd.services.prometheus-jitsi-exporter.after = [ "jitsi-videobridge2.service" ];
303 services.jitsi-videobridge = {
304 enable = true;
305 apis = [ "colibri" "rest" ];
306 };
307 };
308 exporterTest = ''
309 wait_for_unit("jitsi-videobridge2.service")
310 wait_for_open_port(8080)
311 wait_for_unit("prometheus-jitsi-exporter.service")
312 wait_for_open_port(9700)
313 wait_until_succeeds(
314 'journalctl -eu prometheus-jitsi-exporter.service -o cat | grep "key=participants"'
315 )
316 succeed("curl -sSf 'localhost:9700/metrics' | grep 'jitsi_participants 0'")
317 '';
318 };
319
320 json = {
321 exporterConfig = {
322 enable = true;
323 url = "http://localhost";
324 configFile = pkgs.writeText "json-exporter-conf.json" (builtins.toJSON {
325 metrics = [
326 { name = "json_test_metric"; path = "{ .test }"; }
327 ];
328 });
329 };
330 metricProvider = {
331 systemd.services.prometheus-json-exporter.after = [ "nginx.service" ];
332 services.nginx = {
333 enable = true;
334 virtualHosts.localhost.locations."/".extraConfig = ''
335 return 200 "{\"test\":1}";
336 '';
337 };
338 };
339 exporterTest = ''
340 wait_for_unit("nginx.service")
341 wait_for_open_port(80)
342 wait_for_unit("prometheus-json-exporter.service")
343 wait_for_open_port(7979)
344 succeed(
345 "curl -sSf 'localhost:7979/probe?target=http://localhost' | grep 'json_test_metric 1'"
346 )
347 '';
348 };
349
350 kea = let
351 controlSocketPath = "/run/kea/dhcp6.sock";
352 in
353 {
354 exporterConfig = {
355 enable = true;
356 controlSocketPaths = [
357 controlSocketPath
358 ];
359 };
360 metricProvider = {
361 systemd.services.prometheus-kea-exporter.after = [ "kea-dhcp6-server.service" ];
362
363 services.kea = {
364 dhcp6 = {
365 enable = true;
366 settings = {
367 control-socket = {
368 socket-type = "unix";
369 socket-name = controlSocketPath;
370 };
371 };
372 };
373 };
374 };
375
376 exporterTest = ''
377 wait_for_unit("kea-dhcp6-server.service")
378 wait_for_file("${controlSocketPath}")
379 wait_for_unit("prometheus-kea-exporter.service")
380 wait_for_open_port(9547)
381 succeed(
382 "curl --fail localhost:9547/metrics | grep 'packets_received_total'"
383 )
384 '';
385 };
386
387 knot = {
388 exporterConfig = {
389 enable = true;
390 };
391 metricProvider = {
392 services.knot = {
393 enable = true;
394 extraArgs = [ "-v" ];
395 extraConfig = ''
396 server:
397 listen: 127.0.0.1@53
398
399 template:
400 - id: default
401 global-module: mod-stats
402 dnssec-signing: off
403 zonefile-sync: -1
404 journal-db: /var/lib/knot/journal
405 kasp-db: /var/lib/knot/kasp
406 timer-db: /var/lib/knot/timer
407 zonefile-load: difference
408 storage: ${pkgs.buildEnv {
409 name = "foo";
410 paths = [
411 (pkgs.writeTextDir "test.zone" ''
412 @ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
413 @ NS ns1
414 @ NS ns2
415 ns1 A 192.168.0.1
416 '')
417 ];
418 }}
419
420 mod-stats:
421 - id: custom
422 edns-presence: on
423 query-type: on
424
425 zone:
426 - domain: test
427 file: test.zone
428 module: mod-stats/custom
429 '';
430 };
431 };
432 exporterTest = ''
433 wait_for_unit("knot.service")
434 wait_for_unit("prometheus-knot-exporter.service")
435 wait_for_open_port(9433)
436 succeed("curl -sSf 'localhost:9433' | grep 'knot_server_zone_count 1.0'")
437 '';
438 };
439
440 keylight = {
441 # A hardware device is required to properly test this exporter, so just
442 # perform a couple of basic sanity checks that the exporter is running
443 # and requires a target, but cannot reach a specified target.
444 exporterConfig = {
445 enable = true;
446 };
447 exporterTest = ''
448 wait_for_unit("prometheus-keylight-exporter.service")
449 wait_for_open_port(9288)
450 succeed(
451 "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics | grep '400'"
452 )
453 succeed(
454 "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics?target=nosuchdevice | grep '500'"
455 )
456 '';
457 };
458
459 lnd = {
460 exporterConfig = {
461 enable = true;
462 lndTlsPath = "/var/lib/lnd/tls.cert";
463 lndMacaroonDir = "/var/lib/lnd";
464 extraFlags = [ "--lnd.network=regtest" ];
465 };
466 metricProvider = {
467 systemd.services.prometheus-lnd-exporter.serviceConfig.RestartSec = 15;
468 systemd.services.prometheus-lnd-exporter.after = [ "lnd.service" ];
469 services.bitcoind.regtest = {
470 enable = true;
471 extraConfig = ''
472 rpcauth=bitcoinrpc:e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7
473 zmqpubrawblock=tcp://127.0.0.1:28332
474 zmqpubrawtx=tcp://127.0.0.1:28333
475 '';
476 extraCmdlineOptions = [ "-regtest" ];
477 };
478 systemd.services.lnd = {
479 serviceConfig.ExecStart = ''
480 ${pkgs.lnd}/bin/lnd \
481 --datadir=/var/lib/lnd \
482 --tlscertpath=/var/lib/lnd/tls.cert \
483 --tlskeypath=/var/lib/lnd/tls.key \
484 --logdir=/var/log/lnd \
485 --bitcoin.active \
486 --bitcoin.regtest \
487 --bitcoin.node=bitcoind \
488 --bitcoind.rpcuser=bitcoinrpc \
489 --bitcoind.rpcpass=hunter2 \
490 --bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332 \
491 --bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333 \
492 --readonlymacaroonpath=/var/lib/lnd/readonly.macaroon
493 '';
494 serviceConfig.StateDirectory = "lnd";
495 wantedBy = [ "multi-user.target" ];
496 after = [ "network.target" ];
497 };
498 # initialize wallet, creates macaroon needed by exporter
499 systemd.services.lnd.postStart = ''
500 ${pkgs.curl}/bin/curl \
501 --retry 20 \
502 --retry-delay 1 \
503 --retry-connrefused \
504 --cacert /var/lib/lnd/tls.cert \
505 -X GET \
506 https://localhost:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /tmp/seed
507 ${pkgs.curl}/bin/curl \
508 --retry 20 \
509 --retry-delay 1 \
510 --retry-connrefused \
511 --cacert /var/lib/lnd/tls.cert \
512 -X POST \
513 -d "{\"wallet_password\": \"asdfasdfasdf\", \"cipher_seed_mnemonic\": $(cat /tmp/seed | tr -d '\n')}" \
514 https://localhost:8080/v1/initwallet
515 '';
516 };
517 exporterTest = ''
518 wait_for_unit("lnd.service")
519 wait_for_open_port(10009)
520 wait_for_unit("prometheus-lnd-exporter.service")
521 wait_for_open_port(9092)
522 succeed("curl -sSf localhost:9092/metrics | grep '^lnd_peer_count'")
523 '';
524 };
525
526 mail = {
527 exporterConfig = {
528 enable = true;
529 configuration = {
530 monitoringInterval = "2s";
531 mailCheckTimeout = "10s";
532 servers = [{
533 name = "testserver";
534 server = "localhost";
535 port = 25;
536 from = "mail-exporter@localhost";
537 to = "mail-exporter@localhost";
538 detectionDir = "/var/spool/mail/mail-exporter/new";
539 }];
540 };
541 };
542 metricProvider = {
543 services.postfix.enable = true;
544 systemd.services.prometheus-mail-exporter = {
545 after = [ "postfix.service" ];
546 requires = [ "postfix.service" ];
547 preStart = ''
548 mkdir -p -m 0700 mail-exporter/new
549 '';
550 serviceConfig = {
551 ProtectHome = true;
552 ReadOnlyPaths = "/";
553 ReadWritePaths = "/var/spool/mail";
554 WorkingDirectory = "/var/spool/mail";
555 };
556 };
557 users.users.mailexporter = {
558 isSystemUser = true;
559 group = "mailexporter";
560 };
561 users.groups.mailexporter = {};
562 };
563 exporterTest = ''
564 wait_for_unit("postfix.service")
565 wait_for_unit("prometheus-mail-exporter.service")
566 wait_for_open_port(9225)
567 wait_until_succeeds(
568 "curl -sSf http://localhost:9225/metrics | grep 'mail_deliver_success{configname=\"testserver\"} 1'"
569 )
570 '';
571 };
572
573 mikrotik = {
574 exporterConfig = {
575 enable = true;
576 extraFlags = [ "-timeout=1s" ];
577 configuration = {
578 devices = [
579 {
580 name = "router";
581 address = "192.168.42.48";
582 user = "prometheus";
583 password = "shh";
584 }
585 ];
586 features = {
587 bgp = true;
588 dhcp = true;
589 dhcpl = true;
590 dhcpv6 = true;
591 health = true;
592 routes = true;
593 poe = true;
594 pools = true;
595 optics = true;
596 w60g = true;
597 wlansta = true;
598 wlanif = true;
599 monitor = true;
600 ipsec = true;
601 };
602 };
603 };
604 exporterTest = ''
605 wait_for_unit("prometheus-mikrotik-exporter.service")
606 wait_for_open_port(9436)
607 succeed(
608 "curl -sSf http://localhost:9436/metrics | grep 'mikrotik_scrape_collector_success{device=\"router\"} 0'"
609 )
610 '';
611 };
612
613 modemmanager = {
614 exporterConfig = {
615 enable = true;
616 refreshRate = "10s";
617 };
618 metricProvider = {
619 # ModemManager is installed when NetworkManager is enabled. Ensure it is
620 # started and is wanted by NM and the exporter to start everything up
621 # in the right order.
622 networking.networkmanager.enable = true;
623 systemd.services.ModemManager = {
624 enable = true;
625 wantedBy = [ "NetworkManager.service" "prometheus-modemmanager-exporter.service" ];
626 };
627 };
628 exporterTest = ''
629 wait_for_unit("ModemManager.service")
630 wait_for_unit("prometheus-modemmanager-exporter.service")
631 wait_for_open_port(9539)
632 succeed(
633 "curl -sSf http://localhost:9539/metrics | grep 'modemmanager_info'"
634 )
635 '';
636 };
637
638 nextcloud = {
639 exporterConfig = {
640 enable = true;
641 passwordFile = "/var/nextcloud-pwfile";
642 url = "http://localhost";
643 };
644 metricProvider = {
645 systemd.services.nc-pwfile =
646 let
647 passfile = (pkgs.writeText "pwfile" "snakeoilpw");
648 in
649 {
650 requiredBy = [ "prometheus-nextcloud-exporter.service" ];
651 before = [ "prometheus-nextcloud-exporter.service" ];
652 serviceConfig.ExecStart = ''
653 ${pkgs.coreutils}/bin/install -o nextcloud-exporter -m 0400 ${passfile} /var/nextcloud-pwfile
654 '';
655 };
656 services.nginx = {
657 enable = true;
658 virtualHosts."localhost" = {
659 basicAuth.nextcloud-exporter = "snakeoilpw";
660 locations."/" = {
661 root = "${pkgs.prometheus-nextcloud-exporter.src}/serverinfo/testdata";
662 tryFiles = "/negative-space.xml =404";
663 };
664 };
665 };
666 };
667 exporterTest = ''
668 wait_for_unit("nginx.service")
669 wait_for_unit("prometheus-nextcloud-exporter.service")
670 wait_for_open_port(9205)
671 succeed("curl -sSf http://localhost:9205/metrics | grep 'nextcloud_up 1'")
672 '';
673 };
674
675 nginx = {
676 exporterConfig = {
677 enable = true;
678 };
679 metricProvider = {
680 services.nginx = {
681 enable = true;
682 statusPage = true;
683 virtualHosts."test".extraConfig = "return 204;";
684 };
685 };
686 exporterTest = ''
687 wait_for_unit("nginx.service")
688 wait_for_unit("prometheus-nginx-exporter.service")
689 wait_for_open_port(9113)
690 succeed("curl -sSf http://localhost:9113/metrics | grep 'nginx_up 1'")
691 '';
692 };
693
694 nginxlog = {
695 exporterConfig = {
696 enable = true;
697 group = "nginx";
698 settings = {
699 namespaces = [
700 {
701 name = "filelogger";
702 source = {
703 files = [ "/var/log/nginx/filelogger.access.log" ];
704 };
705 }
706 {
707 name = "syslogger";
708 source = {
709 syslog = {
710 listen_address = "udp://127.0.0.1:10000";
711 format = "rfc3164";
712 tags = [ "nginx" ];
713 };
714 };
715 }
716 ];
717 };
718 };
719 metricProvider = {
720 services.nginx = {
721 enable = true;
722 httpConfig = ''
723 server {
724 listen 80;
725 server_name filelogger.local;
726 access_log /var/log/nginx/filelogger.access.log;
727 }
728 server {
729 listen 81;
730 server_name syslogger.local;
731 access_log syslog:server=127.0.0.1:10000,tag=nginx,severity=info;
732 }
733 '';
734 };
735 };
736 exporterTest = ''
737 wait_for_unit("nginx.service")
738 wait_for_unit("prometheus-nginxlog-exporter.service")
739 wait_for_open_port(9117)
740 wait_for_open_port(80)
741 wait_for_open_port(81)
742 succeed("curl http://localhost")
743 execute("sleep 1")
744 succeed(
745 "curl -sSf http://localhost:9117/metrics | grep 'filelogger_http_response_count_total' | grep 1"
746 )
747 succeed("curl http://localhost:81")
748 execute("sleep 1")
749 succeed(
750 "curl -sSf http://localhost:9117/metrics | grep 'syslogger_http_response_count_total' | grep 1"
751 )
752 '';
753 };
754
755 node = {
756 exporterConfig = {
757 enable = true;
758 };
759 exporterTest = ''
760 wait_for_unit("prometheus-node-exporter.service")
761 wait_for_open_port(9100)
762 succeed(
763 "curl -sSf http://localhost:9100/metrics | grep 'node_exporter_build_info{.\\+} 1'"
764 )
765 '';
766 };
767
768 openldap = {
769 exporterConfig = {
770 enable = true;
771 ldapCredentialFile = "${pkgs.writeText "exporter.yml" ''
772 ldapUser: "cn=root,dc=example"
773 ldapPass: "notapassword"
774 ''}";
775 };
776 metricProvider = {
777 services.openldap = {
778 enable = true;
779 settings.children = {
780 "cn=schema".includes = [
781 "${pkgs.openldap}/etc/schema/core.ldif"
782 "${pkgs.openldap}/etc/schema/cosine.ldif"
783 "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
784 "${pkgs.openldap}/etc/schema/nis.ldif"
785 ];
786 "olcDatabase={1}mdb" = {
787 attrs = {
788 objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
789 olcDatabase = "{1}mdb";
790 olcDbDirectory = "/var/db/openldap";
791 olcSuffix = "dc=example";
792 olcRootDN = {
793 # cn=root,dc=example
794 base64 = "Y249cm9vdCxkYz1leGFtcGxl";
795 };
796 olcRootPW = {
797 path = "${pkgs.writeText "rootpw" "notapassword"}";
798 };
799 };
800 };
801 "olcDatabase={2}monitor".attrs = {
802 objectClass = [ "olcDatabaseConfig" ];
803 olcDatabase = "{2}monitor";
804 olcAccess = [ "to dn.subtree=cn=monitor by users read" ];
805 };
806 };
807 declarativeContents."dc=example" = ''
808 dn: dc=example
809 objectClass: domain
810 dc: example
811
812 dn: ou=users,dc=example
813 objectClass: organizationalUnit
814 ou: users
815 '';
816 };
817 };
818 exporterTest = ''
819 wait_for_unit("prometheus-openldap-exporter.service")
820 wait_for_open_port(389)
821 wait_for_open_port(9330)
822 wait_until_succeeds(
823 "curl -sSf http://localhost:9330/metrics | grep 'openldap_scrape{result=\"ok\"} 1'"
824 )
825 '';
826 };
827
828 openvpn = {
829 exporterConfig = {
830 enable = true;
831 group = "openvpn";
832 statusPaths = [ "/run/openvpn-test" ];
833 };
834 metricProvider = {
835 users.groups.openvpn = { };
836 services.openvpn.servers.test = {
837 config = ''
838 dev tun
839 status /run/openvpn-test
840 status-version 3
841 '';
842 up = "chmod g+r /run/openvpn-test";
843 };
844 systemd.services."openvpn-test".serviceConfig.Group = "openvpn";
845 };
846 exporterTest = ''
847 wait_for_unit("openvpn-test.service")
848 wait_for_unit("prometheus-openvpn-exporter.service")
849 succeed("curl -sSf http://localhost:9176/metrics | grep 'openvpn_up{.*} 1'")
850 '';
851 };
852
853 postfix = {
854 exporterConfig = {
855 enable = true;
856 };
857 metricProvider = {
858 services.postfix.enable = true;
859 };
860 exporterTest = ''
861 wait_for_unit("prometheus-postfix-exporter.service")
862 wait_for_file("/var/lib/postfix/queue/public/showq")
863 wait_for_open_port(9154)
864 succeed(
865 "curl -sSf http://localhost:9154/metrics | grep 'postfix_smtpd_connects_total 0'"
866 )
867 succeed("curl -sSf http://localhost:9154/metrics | grep 'postfix_up{.*} 1'")
868 '';
869 };
870
871 postgres = {
872 exporterConfig = {
873 enable = true;
874 runAsLocalSuperUser = true;
875 };
876 metricProvider = {
877 services.postgresql.enable = true;
878 };
879 exporterTest = ''
880 wait_for_unit("prometheus-postgres-exporter.service")
881 wait_for_open_port(9187)
882 wait_for_unit("postgresql.service")
883 succeed(
884 "curl -sSf http://localhost:9187/metrics | grep 'pg_exporter_last_scrape_error 0'"
885 )
886 succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 1'")
887 systemctl("stop postgresql.service")
888 succeed(
889 "curl -sSf http://localhost:9187/metrics | grep -v 'pg_exporter_last_scrape_error 0'"
890 )
891 succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 0'")
892 systemctl("start postgresql.service")
893 wait_for_unit("postgresql.service")
894 succeed(
895 "curl -sSf http://localhost:9187/metrics | grep 'pg_exporter_last_scrape_error 0'"
896 )
897 succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 1'")
898 '';
899 };
900
901 process = {
902 exporterConfig = {
903 enable = true;
904 settings.process_names = [
905 # Remove nix store path from process name
906 { name = "{{.Matches.Wrapped}} {{ .Matches.Args }}"; cmdline = [ "^/nix/store[^ ]*/(?P<Wrapped>[^ /]*) (?P<Args>.*)" ]; }
907 ];
908 };
909 exporterTest = ''
910 wait_for_unit("prometheus-process-exporter.service")
911 wait_for_open_port(9256)
912 wait_until_succeeds(
913 "curl -sSf localhost:9256/metrics | grep -q '{}'".format(
914 'namedprocess_namegroup_cpu_seconds_total{groupname="process-exporter '
915 )
916 )
917 '';
918 };
919
920 py-air-control = {
921 nodeName = "py_air_control";
922 exporterConfig = {
923 enable = true;
924 deviceHostname = "127.0.0.1";
925 };
926 exporterTest = ''
927 wait_for_unit("prometheus-py-air-control-exporter.service")
928 wait_for_open_port(9896)
929 succeed(
930 "curl -sSf http://localhost:9896/metrics | grep 'py_air_control_sampling_error_total'"
931 )
932 '';
933 };
934
935 redis = {
936 exporterConfig = {
937 enable = true;
938 };
939 metricProvider.services.redis.enable = true;
940 exporterTest = ''
941 wait_for_unit("redis.service")
942 wait_for_unit("prometheus-redis-exporter.service")
943 wait_for_open_port(6379)
944 wait_for_open_port(9121)
945 wait_until_succeeds("curl -sSf localhost:9121/metrics | grep 'redis_up 1'")
946 '';
947 };
948
949 rspamd = {
950 exporterConfig = {
951 enable = true;
952 };
953 metricProvider = {
954 services.rspamd.enable = true;
955 };
956 exporterTest = ''
957 wait_for_unit("rspamd.service")
958 wait_for_unit("prometheus-rspamd-exporter.service")
959 wait_for_open_port(11334)
960 wait_for_open_port(7980)
961 wait_until_succeeds(
962 "curl -sSf 'localhost:7980/probe?target=http://localhost:11334/stat' | grep 'rspamd_scanned{host=\"rspamd\"} 0'"
963 )
964 '';
965 };
966
967 rtl_433 = {
968 exporterConfig = {
969 enable = true;
970 };
971 metricProvider = {
972 # Mock rtl_433 binary to return a dummy metric stream.
973 nixpkgs.overlays = [
974 (self: super: {
975 rtl_433 = self.runCommand "rtl_433" { } ''
976 mkdir -p "$out/bin"
977 cat <<EOF > "$out/bin/rtl_433"
978 #!/bin/sh
979 while true; do
980 printf '{"time" : "2020-04-26 13:37:42", "model" : "zopieux", "id" : 55, "channel" : 3, "temperature_C" : 18.000}\n'
981 sleep 4
982 done
983 EOF
984 chmod +x "$out/bin/rtl_433"
985 '';
986 })
987 ];
988 };
989 exporterTest = ''
990 wait_for_unit("prometheus-rtl_433-exporter.service")
991 wait_for_open_port(9550)
992 wait_until_succeeds(
993 "curl -sSf localhost:9550/metrics | grep '{}'".format(
994 'rtl_433_temperature_celsius{channel="3",id="55",location="",model="zopieux"} 18'
995 )
996 )
997 '';
998 };
999
1000 script = {
1001 exporterConfig = {
1002 enable = true;
1003 settings.scripts = [
1004 { name = "success"; script = "sleep 1"; }
1005 ];
1006 };
1007 exporterTest = ''
1008 wait_for_unit("prometheus-script-exporter.service")
1009 wait_for_open_port(9172)
1010 wait_until_succeeds(
1011 "curl -sSf 'localhost:9172/probe?name=success' | grep -q '{}'".format(
1012 'script_success{script="success"} 1'
1013 )
1014 )
1015 '';
1016 };
1017
1018 smokeping = {
1019 exporterConfig = {
1020 enable = true;
1021 hosts = [ "127.0.0.1" ];
1022 };
1023 exporterTest = ''
1024 wait_for_unit("prometheus-smokeping-exporter.service")
1025 wait_for_open_port(9374)
1026 wait_until_succeeds(
1027 "curl -sSf localhost:9374/metrics | grep '{}' | grep -v ' 0$'".format(
1028 'smokeping_requests_total{host="127.0.0.1",ip="127.0.0.1"} '
1029 )
1030 )
1031 wait_until_succeeds(
1032 "curl -sSf localhost:9374/metrics | grep '{}'".format(
1033 'smokeping_response_ttl{host="127.0.0.1",ip="127.0.0.1"}'
1034 )
1035 )
1036 '';
1037 };
1038
1039 snmp = {
1040 exporterConfig = {
1041 enable = true;
1042 configuration.default = {
1043 version = 2;
1044 auth.community = "public";
1045 };
1046 };
1047 exporterTest = ''
1048 wait_for_unit("prometheus-snmp-exporter.service")
1049 wait_for_open_port(9116)
1050 succeed("curl -sSf localhost:9116/metrics | grep 'snmp_request_errors_total 0'")
1051 '';
1052 };
1053
1054 sql = {
1055 exporterConfig = {
1056 configuration.jobs.points = {
1057 interval = "1m";
1058 connections = [
1059 "postgres://prometheus-sql-exporter@/data?host=/run/postgresql&sslmode=disable"
1060 ];
1061 queries = {
1062 points = {
1063 labels = [ "name" ];
1064 help = "Amount of points accumulated per person";
1065 values = [ "amount" ];
1066 query = "SELECT SUM(amount) as amount, name FROM points GROUP BY name";
1067 };
1068 };
1069 };
1070 enable = true;
1071 user = "prometheus-sql-exporter";
1072 };
1073 metricProvider = {
1074 services.postgresql = {
1075 enable = true;
1076 initialScript = builtins.toFile "init.sql" ''
1077 CREATE DATABASE data;
1078 \c data;
1079 CREATE TABLE points (amount INT, name TEXT);
1080 INSERT INTO points(amount, name) VALUES (1, 'jack');
1081 INSERT INTO points(amount, name) VALUES (2, 'jill');
1082 INSERT INTO points(amount, name) VALUES (3, 'jack');
1083
1084 CREATE USER "prometheus-sql-exporter";
1085 GRANT ALL PRIVILEGES ON DATABASE data TO "prometheus-sql-exporter";
1086 GRANT SELECT ON points TO "prometheus-sql-exporter";
1087 '';
1088 };
1089 systemd.services.prometheus-sql-exporter.after = [ "postgresql.service" ];
1090 };
1091 exporterTest = ''
1092 wait_for_unit("prometheus-sql-exporter.service")
1093 wait_for_open_port(9237)
1094 succeed("curl http://localhost:9237/metrics | grep -c 'sql_points{' | grep 2")
1095 '';
1096 };
1097
1098 surfboard = {
1099 exporterConfig = {
1100 enable = true;
1101 modemAddress = "localhost";
1102 };
1103 metricProvider = {
1104 systemd.services.prometheus-surfboard-exporter.after = [ "nginx.service" ];
1105 services.nginx = {
1106 enable = true;
1107 virtualHosts.localhost.locations."/cgi-bin/status".extraConfig = ''
1108 return 204;
1109 '';
1110 };
1111 };
1112 exporterTest = ''
1113 wait_for_unit("nginx.service")
1114 wait_for_open_port(80)
1115 wait_for_unit("prometheus-surfboard-exporter.service")
1116 wait_for_open_port(9239)
1117 succeed("curl -sSf localhost:9239/metrics | grep 'surfboard_up 1'")
1118 '';
1119 };
1120
1121 systemd = {
1122 exporterConfig = {
1123 enable = true;
1124 };
1125 metricProvider = { };
1126 exporterTest = ''
1127 wait_for_unit("prometheus-systemd-exporter.service")
1128 wait_for_open_port(9558)
1129 succeed(
1130 "curl -sSf localhost:9558/metrics | grep '{}'".format(
1131 'systemd_unit_state{name="basic.target",state="active",type="target"} 1'
1132 )
1133 )
1134 '';
1135 };
1136
1137 tor = {
1138 exporterConfig = {
1139 enable = true;
1140 };
1141 metricProvider = {
1142 # Note: this does not connect the test environment to the Tor network.
1143 # Client, relay, bridge or exit connectivity are disabled by default.
1144 services.tor.enable = true;
1145 services.tor.settings.ControlPort = 9051;
1146 };
1147 exporterTest = ''
1148 wait_for_unit("tor.service")
1149 wait_for_open_port(9051)
1150 wait_for_unit("prometheus-tor-exporter.service")
1151 wait_for_open_port(9130)
1152 succeed("curl -sSf localhost:9130/metrics | grep 'tor_version{.\\+} 1'")
1153 '';
1154 };
1155
1156 unifi-poller = {
1157 nodeName = "unifi_poller";
1158 exporterConfig.enable = true;
1159 exporterConfig.controllers = [{ }];
1160 exporterTest = ''
1161 wait_for_unit("prometheus-unifi-poller-exporter.service")
1162 wait_for_open_port(9130)
1163 succeed(
1164 "curl -sSf localhost:9130/metrics | grep 'unifipoller_build_info{.\\+} 1'"
1165 )
1166 '';
1167 };
1168
1169 unbound = {
1170 exporterConfig = {
1171 enable = true;
1172 fetchType = "uds";
1173 controlInterface = "/run/unbound/unbound.ctl";
1174 };
1175 metricProvider = {
1176 services.unbound = {
1177 enable = true;
1178 localControlSocketPath = "/run/unbound/unbound.ctl";
1179 };
1180 systemd.services.prometheus-unbound-exporter.serviceConfig = {
1181 SupplementaryGroups = [ "unbound" ];
1182 };
1183 };
1184 exporterTest = ''
1185 wait_for_unit("unbound.service")
1186 wait_for_unit("prometheus-unbound-exporter.service")
1187 wait_for_open_port(9167)
1188 succeed("curl -sSf localhost:9167/metrics | grep 'unbound_up 1'")
1189 '';
1190 };
1191
1192 varnish = {
1193 exporterConfig = {
1194 enable = true;
1195 instance = "/var/spool/varnish/varnish";
1196 group = "varnish";
1197 };
1198 metricProvider = {
1199 systemd.services.prometheus-varnish-exporter.after = [
1200 "varnish.service"
1201 ];
1202 services.varnish = {
1203 enable = true;
1204 config = ''
1205 vcl 4.0;
1206 backend default {
1207 .host = "127.0.0.1";
1208 .port = "80";
1209 }
1210 '';
1211 };
1212 };
1213 exporterTest = ''
1214 wait_for_unit("prometheus-varnish-exporter.service")
1215 wait_for_open_port(6081)
1216 wait_for_open_port(9131)
1217 succeed("curl -sSf http://localhost:9131/metrics | grep 'varnish_up 1'")
1218 '';
1219 };
1220
1221 wireguard = let snakeoil = import ./wireguard/snakeoil-keys.nix; in
1222 {
1223 exporterConfig.enable = true;
1224 metricProvider = {
1225 networking.wireguard.interfaces.wg0 = {
1226 ips = [ "10.23.42.1/32" "fc00::1/128" ];
1227 listenPort = 23542;
1228
1229 inherit (snakeoil.peer0) privateKey;
1230
1231 peers = singleton {
1232 allowedIPs = [ "10.23.42.2/32" "fc00::2/128" ];
1233
1234 inherit (snakeoil.peer1) publicKey;
1235 };
1236 };
1237 systemd.services.prometheus-wireguard-exporter.after = [ "wireguard-wg0.service" ];
1238 };
1239 exporterTest = ''
1240 wait_for_unit("prometheus-wireguard-exporter.service")
1241 wait_for_open_port(9586)
1242 wait_until_succeeds(
1243 "curl -sSf http://localhost:9586/metrics | grep '${snakeoil.peer1.publicKey}'"
1244 )
1245 '';
1246 };
1247 };
1248in
1249mapAttrs
1250 (exporter: testConfig: (makeTest (
1251 let
1252 nodeName = testConfig.nodeName or exporter;
1253
1254 in
1255 {
1256 name = "prometheus-${exporter}-exporter";
1257
1258 nodes.${nodeName} = mkMerge [{
1259 services.prometheus.exporters.${exporter} = testConfig.exporterConfig;
1260 } testConfig.metricProvider or { }];
1261
1262 testScript = ''
1263 ${nodeName}.start()
1264 ${concatStringsSep "\n" (map (line:
1265 if (builtins.substring 0 1 line == " " || builtins.substring 0 1 line == ")")
1266 then line
1267 else "${nodeName}.${line}"
1268 ) (splitString "\n" (removeSuffix "\n" testConfig.exporterTest)))}
1269 ${nodeName}.shutdown()
1270 '';
1271
1272 meta = with maintainers; {
1273 maintainers = [ willibutz elseym ];
1274 };
1275 }
1276 )))
1277 exporterTests