Release NixOS 23.11
[NixPkgs.git] / nixos / tests / prometheus-exporters.nix
blob7840130d4a36437f199189fa7fb1237ecd33c8f3
1 { system ? builtins.currentSystem
2 , config ? { }
3 , pkgs ? import ../.. { inherit system config; }
4 }:
6 let
7   inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
8   inherit (pkgs.lib) concatStringsSep maintainers mapAttrs mkMerge
9     removeSuffix replaceStrings singleton splitString makeBinPath;
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   */
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     };
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     };
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     };
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;
123           protocol kernel MyObviousTestString {
124             ipv4 {
125               import all;
126               export none;
127             };
128           }
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     };
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     };
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     };
180     collectd = {
181       exporterConfig = {
182         enable = true;
183         extraFlags = [ "--web.collectd-push-path /collectd" ];
184       };
185       exporterTest = let postData = replaceStrings [ "\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     };
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     };
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("curl -sSf 'http://localhost:9222/probe?target=nixos.org'")
238       '';
239     };
241     dovecot = {
242       exporterConfig = {
243         enable = true;
244         scopes = [ "global" ];
245         socketPath = "/var/run/dovecot2/old-stats";
246         user = "root"; # <- don't use user root in production
247       };
248       metricProvider = {
249         services.dovecot2.enable = true;
250       };
251       exporterTest = ''
252         wait_for_unit("prometheus-dovecot-exporter.service")
253         wait_for_open_port(9166)
254         succeed(
255             "curl -sSf http://localhost:9166/metrics | grep 'dovecot_up{scope=\"global\"} 1'"
256         )
257       '';
258     };
260     exportarr-sonarr = {
261       nodeName = "exportarr_sonarr";
262       exporterConfig = {
263         enable = true;
264         url = "http://127.0.0.1:8989";
265         # testing for real data is tricky, because the api key can not be preconfigured
266         apiKeyFile = pkgs.writeText "dummy-api-key" "eccff6a992bc2e4b88e46d064b26bb4e";
267       };
268       exporterTest = ''
269         wait_for_unit("prometheus-exportarr-sonarr-exporter.service")
270         wait_for_open_port(9707)
271         succeed("curl -sSf 'http://localhost:9707/metrics")
272       '';
273     };
275     fastly = {
276       exporterConfig = {
277         enable = true;
278         tokenPath = pkgs.writeText "token" "abc123";
279       };
281       # noop: fastly's exporter can't start without first talking to fastly
282       # see: https://github.com/peterbourgon/fastly-exporter/issues/87
283       exporterTest = ''
284         succeed("true");
285       '';
286     };
288     fritzbox = {
289       # TODO add proper test case
290       exporterConfig = {
291         enable = true;
292       };
293       exporterTest = ''
294         wait_for_unit("prometheus-fritzbox-exporter.service")
295         wait_for_open_port(9133)
296         succeed(
297             "curl -sSf http://localhost:9133/metrics | grep 'fritzbox_exporter_collect_errors 0'"
298         )
299       '';
300     };
302     graphite = {
303       exporterConfig = {
304         enable = true;
305         port = 9108;
306         graphitePort = 9109;
307         mappingSettings.mappings = [{
308           match = "test.*.*";
309           name = "testing";
310           labels = {
311             protocol = "$1";
312             author = "$2";
313           };
314         }];
315       };
316       exporterTest = ''
317         wait_for_unit("prometheus-graphite-exporter.service")
318         wait_for_open_port(9108)
319         wait_for_open_port(9109)
320         succeed("echo test.tcp.foo-bar 1234 $(date +%s) | nc -w1 localhost 9109")
321         succeed("curl -sSf http://localhost:9108/metrics | grep 'testing{author=\"foo-bar\",protocol=\"tcp\"} 1234'")
322       '';
323     };
325     idrac = {
326       exporterConfig = {
327         enable = true;
328         port = 9348;
329         configuration = {
330           hosts = {
331             default = { username = "username"; password = "password"; };
332           };
333         };
334       };
335       exporterTest = ''
336         wait_for_unit("prometheus-idrac-exporter.service")
337         wait_for_open_port(9348)
338         wait_until_succeeds("curl localhost:9348")
339       '';
340     };
342     influxdb = {
343       exporterConfig = {
344         enable = true;
345         sampleExpiry = "3s";
346       };
347       exporterTest = ''
348         wait_for_unit("prometheus-influxdb-exporter.service")
349         wait_for_open_port(9122)
350         succeed(
351           "curl -XPOST http://localhost:9122/write --data-binary 'influxdb_exporter,distro=nixos,added_in=21.09 value=1'"
352         )
353         succeed(
354           "curl -sSf http://localhost:9122/metrics | grep 'nixos'"
355         )
356         execute("sleep 5")
357         fail(
358           "curl -sSf http://localhost:9122/metrics | grep 'nixos'"
359         )
360       '';
361     };
363     ipmi = {
364       exporterConfig = {
365         enable = true;
366       };
367       exporterTest = ''
368         wait_for_unit("prometheus-ipmi-exporter.service")
369         wait_for_open_port(9290)
370         succeed(
371           "curl -sSf http://localhost:9290/metrics | grep 'ipmi_scrape_duration_seconds'"
372         )
373       '';
374     };
376     jitsi = {
377       exporterConfig = {
378         enable = true;
379       };
380       metricProvider = {
381         systemd.services.prometheus-jitsi-exporter.after = [ "jitsi-videobridge2.service" ];
382         services.jitsi-videobridge = {
383           enable = true;
384           colibriRestApi = true;
385         };
386       };
387       exporterTest = ''
388         wait_for_unit("jitsi-videobridge2.service")
389         wait_for_open_port(8080)
390         wait_for_unit("prometheus-jitsi-exporter.service")
391         wait_for_open_port(9700)
392         wait_until_succeeds(
393             'journalctl -eu prometheus-jitsi-exporter.service -o cat | grep "key=participants"'
394         )
395         succeed("curl -sSf 'localhost:9700/metrics' | grep 'jitsi_participants 0'")
396       '';
397     };
399     json = {
400       exporterConfig = {
401         enable = true;
402         url = "http://localhost";
403         configFile = pkgs.writeText "json-exporter-conf.json" (builtins.toJSON {
404           modules = {
405             default = {
406               metrics = [
407                 { name = "json_test_metric"; path = "{ .test }"; }
408               ];
409             };
410           };
411         });
412       };
413       metricProvider = {
414         systemd.services.prometheus-json-exporter.after = [ "nginx.service" ];
415         services.nginx = {
416           enable = true;
417           virtualHosts.localhost.locations."/".extraConfig = ''
418             return 200 "{\"test\":1}";
419           '';
420         };
421       };
422       exporterTest = ''
423         wait_for_unit("nginx.service")
424         wait_for_open_port(80)
425         wait_for_unit("prometheus-json-exporter.service")
426         wait_for_open_port(7979)
427         succeed(
428             "curl -sSf 'localhost:7979/probe?target=http://localhost' | grep 'json_test_metric 1'"
429         )
430       '';
431     };
433     kea = let
434       controlSocketPathV4 = "/run/kea-dhcp4/dhcp4.sock";
435       controlSocketPathV6 = "/run/kea-dhcp6/dhcp6.sock";
436     in
437     {
438       exporterConfig = {
439         enable = true;
440         controlSocketPaths = [
441           controlSocketPathV4
442           controlSocketPathV6
443         ];
444       };
445       metricProvider = {
446         services.kea = {
447           dhcp4 = {
448             enable = true;
449             settings = {
450               control-socket = {
451                 socket-type = "unix";
452                 socket-name = controlSocketPathV4;
453               };
454             };
455           };
456           dhcp6 = {
457             enable = true;
458             settings = {
459               control-socket = {
460                 socket-type = "unix";
461                 socket-name = controlSocketPathV6;
462               };
463             };
464           };
465         };
466       };
468       exporterTest = ''
469         wait_for_unit("kea-dhcp4-server.service")
470         wait_for_unit("kea-dhcp6-server.service")
471         wait_for_file("${controlSocketPathV4}")
472         wait_for_file("${controlSocketPathV6}")
473         wait_for_unit("prometheus-kea-exporter.service")
474         wait_for_open_port(9547)
475         succeed(
476             "curl --fail localhost:9547/metrics | grep 'packets_received_total'"
477         )
478       '';
479     };
481     knot = {
482       exporterConfig = {
483         enable = true;
484       };
485       metricProvider = {
486         services.knot = {
487           enable = true;
488           extraArgs = [ "-v" ];
489           settingsFile = pkgs.writeText "knot.conf" ''
490             server:
491               listen: 127.0.0.1@53
493             template:
494               - id: default
495                 global-module: mod-stats
496                 dnssec-signing: off
497                 zonefile-sync: -1
498                 journal-db: /var/lib/knot/journal
499                 kasp-db: /var/lib/knot/kasp
500                 timer-db: /var/lib/knot/timer
501                 zonefile-load: difference
502                 storage: ${pkgs.buildEnv {
503                   name = "foo";
504                   paths = [
505                     (pkgs.writeTextDir "test.zone" ''
506                       @ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
507                       @       NS      ns1
508                       @       NS      ns2
509                       ns1     A       192.168.0.1
510                     '')
511                   ];
512                 }}
514             mod-stats:
515               - id: custom
516                 edns-presence: on
517                 query-type: on
519             zone:
520               - domain: test
521                 file: test.zone
522                 module: mod-stats/custom
523           '';
524         };
525       };
526       exporterTest = ''
527         wait_for_unit("knot.service")
528         wait_for_unit("prometheus-knot-exporter.service")
529         wait_for_open_port(9433)
530         succeed("curl -sSf 'localhost:9433' | grep '2\.019031301'")
531       '';
532     };
534     keylight = {
535       # A hardware device is required to properly test this exporter, so just
536       # perform a couple of basic sanity checks that the exporter is running
537       # and requires a target, but cannot reach a specified target.
538       exporterConfig = {
539         enable = true;
540       };
541       exporterTest = ''
542         wait_for_unit("prometheus-keylight-exporter.service")
543         wait_for_open_port(9288)
544         succeed(
545             "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics | grep '400'"
546         )
547         succeed(
548             "curl -sS --write-out '%{http_code}' -o /dev/null http://localhost:9288/metrics?target=nosuchdevice | grep '500'"
549         )
550       '';
551     };
553     lnd = {
554       exporterConfig = {
555         enable = true;
556         lndTlsPath = "/var/lib/lnd/tls.cert";
557         lndMacaroonDir = "/var/lib/lnd";
558         extraFlags = [ "--lnd.network=regtest" ];
559       };
560       metricProvider = {
561         systemd.services.prometheus-lnd-exporter.serviceConfig.RestartSec = 15;
562         systemd.services.prometheus-lnd-exporter.after = [ "lnd.service" ];
563         services.bitcoind.regtest = {
564           enable = true;
565           extraConfig = ''
566             rpcauth=bitcoinrpc:e8fe33f797e698ac258c16c8d7aadfbe$872bdb8f4d787367c26bcfd75e6c23c4f19d44a69f5d1ad329e5adf3f82710f7
567             zmqpubrawblock=tcp://127.0.0.1:28332
568             zmqpubrawtx=tcp://127.0.0.1:28333
569           '';
570           extraCmdlineOptions = [ "-regtest" ];
571         };
572         systemd.services.lnd = {
573           serviceConfig.ExecStart = ''
574             ${pkgs.lnd}/bin/lnd \
575               --datadir=/var/lib/lnd \
576               --tlscertpath=/var/lib/lnd/tls.cert \
577               --tlskeypath=/var/lib/lnd/tls.key \
578               --logdir=/var/log/lnd \
579               --bitcoin.active \
580               --bitcoin.regtest \
581               --bitcoin.node=bitcoind \
582               --bitcoind.rpcuser=bitcoinrpc \
583               --bitcoind.rpcpass=hunter2 \
584               --bitcoind.zmqpubrawblock=tcp://127.0.0.1:28332 \
585               --bitcoind.zmqpubrawtx=tcp://127.0.0.1:28333 \
586               --readonlymacaroonpath=/var/lib/lnd/readonly.macaroon
587           '';
588           serviceConfig.StateDirectory = "lnd";
589           wantedBy = [ "multi-user.target" ];
590           after = [ "network.target" ];
591         };
592         # initialize wallet, creates macaroon needed by exporter
593         systemd.services.lnd.postStart = ''
594           ${pkgs.curl}/bin/curl \
595             --retry 20 \
596             --retry-delay 1 \
597             --retry-connrefused \
598             --cacert /var/lib/lnd/tls.cert \
599             -X GET \
600             https://localhost:8080/v1/genseed | ${pkgs.jq}/bin/jq -c '.cipher_seed_mnemonic' > /tmp/seed
601           ${pkgs.curl}/bin/curl \
602             --retry 20 \
603             --retry-delay 1 \
604             --retry-connrefused \
605             --cacert /var/lib/lnd/tls.cert \
606             -X POST \
607             -d "{\"wallet_password\": \"asdfasdfasdf\", \"cipher_seed_mnemonic\": $(cat /tmp/seed | tr -d '\n')}" \
608             https://localhost:8080/v1/initwallet
609         '';
610       };
611       exporterTest = ''
612         wait_for_unit("lnd.service")
613         wait_for_open_port(10009)
614         wait_for_unit("prometheus-lnd-exporter.service")
615         wait_for_open_port(9092)
616         succeed("curl -sSf localhost:9092/metrics | grep '^lnd_peer_count'")
617       '';
618     };
620     mail = {
621       exporterConfig = {
622         enable = true;
623         configuration = {
624           monitoringInterval = "2s";
625           mailCheckTimeout = "10s";
626           servers = [{
627             name = "testserver";
628             server = "localhost";
629             port = 25;
630             from = "mail-exporter@localhost";
631             to = "mail-exporter@localhost";
632             detectionDir = "/var/spool/mail/mail-exporter/new";
633           }];
634         };
635       };
636       metricProvider = {
637         services.postfix.enable = true;
638         systemd.services.prometheus-mail-exporter = {
639           after = [ "postfix.service" ];
640           requires = [ "postfix.service" ];
641           serviceConfig = {
642             ExecStartPre = [
643               "${pkgs.writeShellScript "create-maildir" ''
644                 mkdir -p -m 0700 mail-exporter/new
645               ''}"
646             ];
647             ProtectHome = true;
648             ReadOnlyPaths = "/";
649             ReadWritePaths = "/var/spool/mail";
650             WorkingDirectory = "/var/spool/mail";
651           };
652         };
653         users.users.mailexporter = {
654           isSystemUser = true;
655           group = "mailexporter";
656         };
657         users.groups.mailexporter = {};
658       };
659       exporterTest = ''
660         wait_for_unit("postfix.service")
661         wait_for_unit("prometheus-mail-exporter.service")
662         wait_for_open_port(9225)
663         wait_until_succeeds(
664             "curl -sSf http://localhost:9225/metrics | grep 'mail_deliver_success{configname=\"testserver\"} 1'"
665         )
666       '';
667     };
669     mikrotik = {
670       exporterConfig = {
671         enable = true;
672         extraFlags = [ "-timeout=1s" ];
673         configuration = {
674           devices = [
675             {
676               name = "router";
677               address = "192.168.42.48";
678               user = "prometheus";
679               password = "shh";
680             }
681           ];
682           features = {
683             bgp = true;
684             dhcp = true;
685             dhcpl = true;
686             dhcpv6 = true;
687             health = true;
688             routes = true;
689             poe = true;
690             pools = true;
691             optics = true;
692             w60g = true;
693             wlansta = true;
694             wlanif = true;
695             monitor = true;
696             ipsec = true;
697           };
698         };
699       };
700       exporterTest = ''
701         wait_for_unit("prometheus-mikrotik-exporter.service")
702         wait_for_open_port(9436)
703         succeed(
704             "curl -sSf http://localhost:9436/metrics | grep 'mikrotik_scrape_collector_success{device=\"router\"} 0'"
705         )
706       '';
707     };
709     modemmanager = {
710       exporterConfig = {
711         enable = true;
712         refreshRate = "10s";
713       };
714       metricProvider = {
715         # ModemManager is installed when NetworkManager is enabled. Ensure it is
716         # started and is wanted by NM and the exporter to start everything up
717         # in the right order.
718         networking.networkmanager.enable = true;
719         systemd.services.ModemManager = {
720           enable = true;
721           wantedBy = [ "NetworkManager.service" "prometheus-modemmanager-exporter.service" ];
722         };
723       };
724       exporterTest = ''
725         wait_for_unit("ModemManager.service")
726         wait_for_unit("prometheus-modemmanager-exporter.service")
727         wait_for_open_port(9539)
728         succeed(
729             "curl -sSf http://localhost:9539/metrics | grep 'modemmanager_info'"
730         )
731       '';
732     };
734     mysqld = {
735       exporterConfig = {
736         enable = true;
737         runAsLocalSuperUser = true;
738         configFile = pkgs.writeText "test-prometheus-exporter-mysqld-config.my-cnf" ''
739           [client]
740           user = exporter
741           password = snakeoilpassword
742         '';
743       };
744       metricProvider = {
745         services.mysql = {
746           enable = true;
747           package = pkgs.mariadb;
748           initialScript = pkgs.writeText "mysql-init-script.sql" ''
749             CREATE USER 'exporter'@'localhost'
750             IDENTIFIED BY 'snakeoilpassword'
751             WITH MAX_USER_CONNECTIONS 3;
752             GRANT PROCESS, REPLICATION CLIENT, SLAVE MONITOR, SELECT ON *.* TO 'exporter'@'localhost';
753           '';
754         };
755       };
756       exporterTest = ''
757         wait_for_unit("prometheus-mysqld-exporter.service")
758         wait_for_open_port(9104)
759         wait_for_unit("mysql.service")
760         succeed("curl -sSf http://localhost:9104/metrics | grep 'mysql_up 1'")
761         systemctl("stop mysql.service")
762         succeed("curl -sSf http://localhost:9104/metrics | grep 'mysql_up 0'")
763         systemctl("start mysql.service")
764         wait_for_unit("mysql.service")
765         succeed("curl -sSf http://localhost:9104/metrics | grep 'mysql_up 1'")
766       '';
767     };
769     nextcloud = {
770       exporterConfig = {
771         enable = true;
772         passwordFile = "/var/nextcloud-pwfile";
773         url = "http://localhost";
774       };
775       metricProvider = {
776         systemd.services.nc-pwfile =
777           let
778             passfile = (pkgs.writeText "pwfile" "snakeoilpw");
779           in
780           {
781             requiredBy = [ "prometheus-nextcloud-exporter.service" ];
782             before = [ "prometheus-nextcloud-exporter.service" ];
783             serviceConfig.ExecStart = ''
784               ${pkgs.coreutils}/bin/install -o nextcloud-exporter -m 0400 ${passfile} /var/nextcloud-pwfile
785             '';
786           };
787         services.nginx = {
788           enable = true;
789           virtualHosts."localhost" = {
790             basicAuth.nextcloud-exporter = "snakeoilpw";
791             locations."/" = {
792               root = "${pkgs.prometheus-nextcloud-exporter.src}/serverinfo/testdata";
793               tryFiles = "/negative-space.json =404";
794             };
795           };
796         };
797       };
798       exporterTest = ''
799         wait_for_unit("nginx.service")
800         wait_for_unit("prometheus-nextcloud-exporter.service")
801         wait_for_open_port(9205)
802         succeed("curl -sSf http://localhost:9205/metrics | grep 'nextcloud_up 1'")
803       '';
804     };
806     nginx = {
807       exporterConfig = {
808         enable = true;
809       };
810       metricProvider = {
811         services.nginx = {
812           enable = true;
813           statusPage = true;
814           virtualHosts."test".extraConfig = "return 204;";
815         };
816       };
817       exporterTest = ''
818         wait_for_unit("nginx.service")
819         wait_for_unit("prometheus-nginx-exporter.service")
820         wait_for_open_port(9113)
821         succeed("curl -sSf http://localhost:9113/metrics | grep 'nginx_up 1'")
822       '';
823     };
825     nginxlog = {
826       exporterConfig = {
827         enable = true;
828         group = "nginx";
829         settings = {
830           namespaces = [
831             {
832               name = "filelogger";
833               source = {
834                 files = [ "/var/log/nginx/filelogger.access.log" ];
835               };
836             }
837             {
838               name = "syslogger";
839               source = {
840                 syslog = {
841                   listen_address = "udp://127.0.0.1:10000";
842                   format = "rfc3164";
843                   tags = [ "nginx" ];
844                 };
845               };
846             }
847           ];
848         };
849       };
850       metricProvider = {
851         services.nginx = {
852           enable = true;
853           httpConfig = ''
854             server {
855               listen 80;
856               server_name filelogger.local;
857               access_log /var/log/nginx/filelogger.access.log;
858             }
859             server {
860               listen 81;
861               server_name syslogger.local;
862               access_log syslog:server=127.0.0.1:10000,tag=nginx,severity=info;
863             }
864           '';
865         };
866       };
867       exporterTest = ''
868         wait_for_unit("nginx.service")
869         wait_for_unit("prometheus-nginxlog-exporter.service")
870         wait_for_open_port(9117)
871         wait_for_open_port(80)
872         wait_for_open_port(81)
873         succeed("curl http://localhost")
874         execute("sleep 1")
875         succeed(
876             "curl -sSf http://localhost:9117/metrics | grep 'filelogger_http_response_count_total' | grep 1"
877         )
878         succeed("curl http://localhost:81")
879         execute("sleep 1")
880         succeed(
881             "curl -sSf http://localhost:9117/metrics | grep 'syslogger_http_response_count_total' | grep 1"
882         )
883       '';
884     };
886     node = {
887       exporterConfig = {
888         enable = true;
889       };
890       exporterTest = ''
891         wait_for_unit("prometheus-node-exporter.service")
892         wait_for_open_port(9100)
893         succeed(
894             "curl -sSf http://localhost:9100/metrics | grep 'node_exporter_build_info{.\\+} 1'"
895         )
896       '';
897     };
899     openldap = {
900       exporterConfig = {
901         enable = true;
902         ldapCredentialFile = "${pkgs.writeText "exporter.yml" ''
903           ldapUser: "cn=root,dc=example"
904           ldapPass: "notapassword"
905         ''}";
906       };
907       metricProvider = {
908         services.openldap = {
909           enable = true;
910           settings.children = {
911             "cn=schema".includes = [
912               "${pkgs.openldap}/etc/schema/core.ldif"
913               "${pkgs.openldap}/etc/schema/cosine.ldif"
914               "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
915               "${pkgs.openldap}/etc/schema/nis.ldif"
916             ];
917             "olcDatabase={1}mdb" = {
918               attrs = {
919                 objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
920                 olcDatabase = "{1}mdb";
921                 olcDbDirectory = "/var/db/openldap";
922                 olcSuffix = "dc=example";
923                 olcRootDN = {
924                   # cn=root,dc=example
925                   base64 = "Y249cm9vdCxkYz1leGFtcGxl";
926                 };
927                 olcRootPW = {
928                   path = "${pkgs.writeText "rootpw" "notapassword"}";
929                 };
930               };
931             };
932             "olcDatabase={2}monitor".attrs = {
933               objectClass = [ "olcDatabaseConfig" ];
934               olcDatabase = "{2}monitor";
935               olcAccess = [ "to dn.subtree=cn=monitor by users read" ];
936             };
937           };
938           declarativeContents."dc=example" = ''
939             dn: dc=example
940             objectClass: domain
941             dc: example
943             dn: ou=users,dc=example
944             objectClass: organizationalUnit
945             ou: users
946           '';
947         };
948       };
949       exporterTest = ''
950         wait_for_unit("prometheus-openldap-exporter.service")
951         wait_for_open_port(389)
952         wait_for_open_port(9330)
953         wait_until_succeeds(
954             "curl -sSf http://localhost:9330/metrics | grep 'openldap_scrape{result=\"ok\"} 1'"
955         )
956       '';
957     };
959     openvpn = {
960       exporterConfig = {
961         enable = true;
962         group = "openvpn";
963         statusPaths = [ "/run/openvpn-test" ];
964       };
965       metricProvider = {
966         users.groups.openvpn = { };
967         services.openvpn.servers.test = {
968           config = ''
969             dev tun
970             status /run/openvpn-test
971             status-version 3
972           '';
973           up = "chmod g+r /run/openvpn-test";
974         };
975         systemd.services."openvpn-test".serviceConfig.Group = "openvpn";
976       };
977       exporterTest = ''
978         wait_for_unit("openvpn-test.service")
979         wait_for_unit("prometheus-openvpn-exporter.service")
980         succeed("curl -sSf http://localhost:9176/metrics | grep 'openvpn_up{.*} 1'")
981       '';
982     };
984     pgbouncer = {
985       exporterConfig = {
986         enable = true;
987         connectionStringFile = pkgs.writeText "connection.conf" "postgres://admin:@localhost:6432/pgbouncer?sslmode=disable";
988       };
990       metricProvider = {
991         services.postgresql.enable = true;
992         services.pgbouncer = {
993           # https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration
994           ignoreStartupParameters = "extra_float_digits";
995           enable = true;
996           listenAddress = "*";
997           databases = { postgres = "host=/run/postgresql/ port=5432 auth_user=postgres dbname=postgres"; };
998           authType = "any";
999           maxClientConn = 99;
1000         };
1001       };
1002       exporterTest = ''
1003         wait_for_unit("postgresql.service")
1004         wait_for_unit("pgbouncer.service")
1005         wait_for_unit("prometheus-pgbouncer-exporter.service")
1006         wait_for_open_port(9127)
1007         succeed("curl -sSf http://localhost:9127/metrics | grep 'pgbouncer_up 1'")
1008         succeed(
1009             "curl -sSf http://localhost:9127/metrics | grep 'pgbouncer_config_max_client_connections 99'"
1010         )
1011       '';
1012     };
1014     php-fpm = {
1015       nodeName = "php_fpm";
1016       exporterConfig = {
1017         enable = true;
1018         environmentFile = pkgs.writeTextFile {
1019           name = "/tmp/prometheus-php-fpm-exporter.env";
1020           text = ''
1021             PHP_FPM_SCRAPE_URI="tcp://127.0.0.1:9000/status"
1022           '';
1023         };
1024       };
1025       metricProvider = {
1026         users.users."php-fpm-exporter" = {
1027           isSystemUser = true;
1028           group  = "php-fpm-exporter";
1029         };
1030         users.groups."php-fpm-exporter" = {};
1031         services.phpfpm.pools."php-fpm-exporter" = {
1032           user = "php-fpm-exporter";
1033           group = "php-fpm-exporter";
1034           settings = {
1035             "pm" = "dynamic";
1036             "pm.max_children" = 32;
1037             "pm.max_requests" = 500;
1038             "pm.start_servers" = 2;
1039             "pm.min_spare_servers" = 2;
1040             "pm.max_spare_servers" = 5;
1041             "pm.status_path" = "/status";
1042             "listen" = "127.0.0.1:9000";
1043             "listen.allowed_clients" = "127.0.0.1";
1044           };
1045           phpEnv."PATH" = makeBinPath [ pkgs.php ];
1046         };
1047       };
1048       exporterTest = ''
1049         wait_for_unit("phpfpm-php-fpm-exporter.service")
1050         wait_for_unit("prometheus-php-fpm-exporter.service")
1051         succeed("curl -sSf http://localhost:9253/metrics | grep 'phpfpm_up{.*} 1'")
1052       '';
1053     };
1055     postfix = {
1056       exporterConfig = {
1057         enable = true;
1058       };
1059       metricProvider = {
1060         services.postfix.enable = true;
1061       };
1062       exporterTest = ''
1063         wait_for_unit("prometheus-postfix-exporter.service")
1064         wait_for_file("/var/lib/postfix/queue/public/showq")
1065         wait_for_open_port(9154)
1066         wait_until_succeeds(
1067             "curl -sSf http://localhost:9154/metrics | grep 'postfix_up{path=\"/var/lib/postfix/queue/public/showq\"} 1'"
1068         )
1069         succeed(
1070             "curl -sSf http://localhost:9154/metrics | grep 'postfix_smtpd_connects_total 0'"
1071         )
1072         succeed("curl -sSf http://localhost:9154/metrics | grep 'postfix_up{.*} 1'")
1073       '';
1074     };
1076     postgres = {
1077       exporterConfig = {
1078         enable = true;
1079         runAsLocalSuperUser = true;
1080       };
1081       metricProvider = {
1082         services.postgresql.enable = true;
1083       };
1084       exporterTest = ''
1085         wait_for_unit("prometheus-postgres-exporter.service")
1086         wait_for_open_port(9187)
1087         wait_for_unit("postgresql.service")
1088         succeed(
1089             "curl -sSf http://localhost:9187/metrics | grep 'pg_exporter_last_scrape_error 0'"
1090         )
1091         succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 1'")
1092         systemctl("stop postgresql.service")
1093         succeed(
1094             "curl -sSf http://localhost:9187/metrics | grep -v 'pg_exporter_last_scrape_error 0'"
1095         )
1096         succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 0'")
1097         systemctl("start postgresql.service")
1098         wait_for_unit("postgresql.service")
1099         succeed(
1100             "curl -sSf http://localhost:9187/metrics | grep 'pg_exporter_last_scrape_error 0'"
1101         )
1102         succeed("curl -sSf http://localhost:9187/metrics | grep 'pg_up 1'")
1103       '';
1104     };
1106     process = {
1107       exporterConfig = {
1108         enable = true;
1109         settings.process_names = [
1110           # Remove nix store path from process name
1111           { name = "{{.Matches.Wrapped}} {{ .Matches.Args }}"; cmdline = [ "^/nix/store[^ ]*/(?P<Wrapped>[^ /]*) (?P<Args>.*)" ]; }
1112         ];
1113       };
1114       exporterTest = ''
1115         wait_for_unit("prometheus-process-exporter.service")
1116         wait_for_open_port(9256)
1117         wait_until_succeeds(
1118             "curl -sSf localhost:9256/metrics | grep -q '{}'".format(
1119                 'namedprocess_namegroup_cpu_seconds_total{groupname="process-exporter '
1120             )
1121         )
1122       '';
1123     };
1125     pve = let
1126       pveExporterEnvFile = pkgs.writeTextFile {
1127         name = "pve.env";
1128         text = ''
1129           PVE_USER="test_user@pam"
1130           PVE_PASSWORD="hunter3"
1131           PVE_VERIFY_SSL="false"
1132         '';
1133       };
1134     in {
1135       exporterConfig = {
1136         enable = true;
1137         environmentFile = pveExporterEnvFile;
1138       };
1139       exporterTest = ''
1140         wait_for_unit("prometheus-pve-exporter.service")
1141         wait_for_open_port(9221)
1142         wait_until_succeeds("curl localhost:9221")
1143       '';
1144     };
1146     py-air-control = {
1147       nodeName = "py_air_control";
1148       exporterConfig = {
1149         enable = true;
1150         deviceHostname = "127.0.0.1";
1151       };
1152       exporterTest = ''
1153         wait_for_unit("prometheus-py-air-control-exporter.service")
1154         wait_for_open_port(9896)
1155         succeed(
1156             "curl -sSf http://localhost:9896/metrics | grep 'py_air_control_sampling_error_total'"
1157         )
1158       '';
1159     };
1161     redis = {
1162       exporterConfig = {
1163         enable = true;
1164       };
1165       metricProvider.services.redis.servers."".enable = true;
1166       exporterTest = ''
1167         wait_for_unit("redis.service")
1168         wait_for_unit("prometheus-redis-exporter.service")
1169         wait_for_open_port(6379)
1170         wait_for_open_port(9121)
1171         wait_until_succeeds("curl -sSf localhost:9121/metrics | grep 'redis_up 1'")
1172       '';
1173     };
1175     rspamd = {
1176       exporterConfig = {
1177         enable = true;
1178       };
1179       metricProvider = {
1180         services.rspamd.enable = true;
1181       };
1182       exporterTest = ''
1183         wait_for_unit("rspamd.service")
1184         wait_for_unit("prometheus-rspamd-exporter.service")
1185         wait_for_open_port(11334)
1186         wait_for_open_port(7980)
1187         wait_until_succeeds(
1188             "curl -sSf 'localhost:7980/probe?target=http://localhost:11334/stat' | grep 'rspamd_scanned{host=\"rspamd\"} 0'"
1189         )
1190       '';
1191     };
1193     rtl_433 = {
1194       exporterConfig = {
1195         enable = true;
1196       };
1197       metricProvider = {
1198         # Mock rtl_433 binary to return a dummy metric stream.
1199         nixpkgs.overlays = [
1200           (self: super: {
1201             rtl_433 = self.runCommand "rtl_433" { } ''
1202               mkdir -p "$out/bin"
1203               cat <<EOF > "$out/bin/rtl_433"
1204               #!/bin/sh
1205               while true; do
1206                 printf '{"time" : "2020-04-26 13:37:42", "model" : "zopieux", "id" : 55, "channel" : 3, "temperature_C" : 18.000}\n'
1207                 sleep 4
1208               done
1209               EOF
1210               chmod +x "$out/bin/rtl_433"
1211             '';
1212           })
1213         ];
1214       };
1215       exporterTest = ''
1216         wait_for_unit("prometheus-rtl_433-exporter.service")
1217         wait_for_open_port(9550)
1218         wait_until_succeeds(
1219             "curl -sSf localhost:9550/metrics | grep '{}'".format(
1220                 'rtl_433_temperature_celsius{channel="3",id="55",location="",model="zopieux"} 18'
1221             )
1222         )
1223       '';
1224     };
1226     sabnzbd = {
1227       exporterConfig = {
1228         enable = true;
1229         servers = [{
1230           baseUrl = "http://localhost:8080";
1231           apiKeyFile = "/var/sabnzbd-apikey";
1232         }];
1233       };
1235       metricProvider = {
1236         services.sabnzbd.enable = true;
1238         # unrar is required for sabnzbd
1239         nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (pkgs.lib.getName pkg) [ "unrar" ];
1241         # extract the generated api key before starting
1242         systemd.services.sabnzbd-apikey = {
1243           requires = [ "sabnzbd.service" ];
1244           after = [ "sabnzbd.service" ];
1245           requiredBy = [ "prometheus-sabnzbd-exporter.service" ];
1246           before = [ "prometheus-sabnzbd-exporter.service" ];
1247           script = ''
1248             grep -Po '^api_key = \K.+' /var/lib/sabnzbd/sabnzbd.ini > /var/sabnzbd-apikey
1249           '';
1250         };
1251       };
1253       exporterTest = ''
1254         wait_for_unit("sabnzbd.service")
1255         wait_for_unit("prometheus-sabnzbd-exporter.service")
1256         wait_for_open_port(8080)
1257         wait_for_open_port(9387)
1258         wait_until_succeeds(
1259             "curl -sSf 'localhost:9387/metrics' | grep 'sabnzbd_queue_size{sabnzbd_instance=\"http://localhost:8080\"} 0.0'"
1260         )
1261       '';
1262     };
1264     scaphandre = {
1265       exporterConfig = {
1266         enable = true;
1267       };
1268       metricProvider = {
1269         boot.kernelModules = [ "intel_rapl_common" ];
1270       };
1271       exporterTest = ''
1272         wait_for_unit("prometheus-scaphandre-exporter.service")
1273         wait_for_open_port(8080)
1274         wait_until_succeeds(
1275             "curl -sSf 'localhost:8080/metrics'"
1276         )
1277       '';
1278     };
1280     shelly = {
1281       exporterConfig = {
1282         enable = true;
1283         metrics-file = "${pkgs.writeText "test.json" ''{}''}";
1284       };
1285       exporterTest = ''
1286         wait_for_unit("prometheus-shelly-exporter.service")
1287         wait_for_open_port(9784)
1288         wait_until_succeeds(
1289             "curl -sSf 'localhost:9784/metrics'"
1290         )
1291       '';
1292     };
1294     script = {
1295       exporterConfig = {
1296         enable = true;
1297         settings.scripts = [
1298           { name = "success"; script = "sleep 1"; }
1299         ];
1300       };
1301       exporterTest = ''
1302         wait_for_unit("prometheus-script-exporter.service")
1303         wait_for_open_port(9172)
1304         wait_until_succeeds(
1305             "curl -sSf 'localhost:9172/probe?name=success' | grep -q '{}'".format(
1306                 'script_success{script="success"} 1'
1307             )
1308         )
1309       '';
1310     };
1312     smartctl = {
1313       exporterConfig = {
1314         enable = true;
1315         devices = [
1316           "/dev/vda"
1317         ];
1318       };
1319       exporterTest = ''
1320         wait_until_succeeds(
1321             'journalctl -eu prometheus-smartctl-exporter.service -o cat | grep "Unable to detect device type"'
1322         )
1323       '';
1324     };
1326     smokeping = {
1327       exporterConfig = {
1328         enable = true;
1329         hosts = [ "127.0.0.1" ];
1330       };
1331       exporterTest = ''
1332         wait_for_unit("prometheus-smokeping-exporter.service")
1333         wait_for_open_port(9374)
1334         wait_until_succeeds(
1335             "curl -sSf localhost:9374/metrics | grep '{}' | grep -v ' 0$'".format(
1336                 'smokeping_requests_total{host="127.0.0.1",ip="127.0.0.1",source=""} '
1337             )
1338         )
1339         wait_until_succeeds(
1340             "curl -sSf localhost:9374/metrics | grep '{}'".format(
1341                 'smokeping_response_ttl{host="127.0.0.1",ip="127.0.0.1",source=""}'
1342             )
1343         )
1344       '';
1345     };
1347     snmp = {
1348       exporterConfig = {
1349         enable = true;
1350         configuration.default = {
1351           version = 2;
1352           auth.community = "public";
1353         };
1354       };
1355       exporterTest = ''
1356         wait_for_unit("prometheus-snmp-exporter.service")
1357         wait_for_open_port(9116)
1358         succeed("curl -sSf localhost:9116/metrics | grep 'snmp_request_errors_total 0'")
1359       '';
1360     };
1362     sql = {
1363       exporterConfig = {
1364         configuration.jobs.points = {
1365           interval = "1m";
1366           connections = [
1367             "postgres://prometheus-sql-exporter@/data?host=/run/postgresql&sslmode=disable"
1368           ];
1369           queries = {
1370             points = {
1371               labels = [ "name" ];
1372               help = "Amount of points accumulated per person";
1373               values = [ "amount" ];
1374               query = "SELECT SUM(amount) as amount, name FROM points GROUP BY name";
1375             };
1376           };
1377         };
1378         enable = true;
1379         user = "prometheus-sql-exporter";
1380       };
1381       metricProvider = {
1382         services.postgresql = {
1383           enable = true;
1384           initialScript = builtins.toFile "init.sql" ''
1385             CREATE DATABASE data;
1386             \c data;
1387             CREATE TABLE points (amount INT, name TEXT);
1388             INSERT INTO points(amount, name) VALUES (1, 'jack');
1389             INSERT INTO points(amount, name) VALUES (2, 'jill');
1390             INSERT INTO points(amount, name) VALUES (3, 'jack');
1392             CREATE USER "prometheus-sql-exporter";
1393             GRANT ALL PRIVILEGES ON DATABASE data TO "prometheus-sql-exporter";
1394             GRANT SELECT ON points TO "prometheus-sql-exporter";
1395           '';
1396         };
1397         systemd.services.prometheus-sql-exporter.after = [ "postgresql.service" ];
1398       };
1399       exporterTest = ''
1400         wait_for_unit("prometheus-sql-exporter.service")
1401         wait_for_open_port(9237)
1402         succeed("curl http://localhost:9237/metrics | grep -c 'sql_points{' | grep 2")
1403       '';
1404     };
1406     statsd = {
1407       exporterConfig = {
1408         enable = true;
1409       };
1410       exporterTest = ''
1411         wait_for_unit("prometheus-statsd-exporter.service")
1412         wait_for_open_port(9102)
1413         succeed("curl http://localhost:9102/metrics | grep 'statsd_exporter_build_info{'")
1414         wait_until_succeeds(
1415           "echo 'test.udp:1|c' > /dev/udp/localhost/9125 && \
1416           curl http://localhost:9102/metrics | grep 'test_udp 1'",
1417           timeout=10
1418         )
1419         wait_until_succeeds(
1420           "echo 'test.tcp:1|c' > /dev/tcp/localhost/9125 && \
1421           curl http://localhost:9102/metrics | grep 'test_tcp 1'",
1422           timeout=10
1423         )
1424       '';
1425     };
1427     surfboard = {
1428       exporterConfig = {
1429         enable = true;
1430         modemAddress = "localhost";
1431       };
1432       metricProvider = {
1433         systemd.services.prometheus-surfboard-exporter.after = [ "nginx.service" ];
1434         services.nginx = {
1435           enable = true;
1436           virtualHosts.localhost.locations."/cgi-bin/status".extraConfig = ''
1437             return 204;
1438           '';
1439         };
1440       };
1441       exporterTest = ''
1442         wait_for_unit("nginx.service")
1443         wait_for_open_port(80)
1444         wait_for_unit("prometheus-surfboard-exporter.service")
1445         wait_for_open_port(9239)
1446         succeed("curl -sSf localhost:9239/metrics | grep 'surfboard_up 1'")
1447       '';
1448     };
1450     systemd = {
1451       exporterConfig = {
1452         enable = true;
1454         extraFlags = [
1455           "--systemd.collector.enable-restart-count"
1456         ];
1457       };
1458       metricProvider = { };
1459       exporterTest = ''
1460         wait_for_unit("prometheus-systemd-exporter.service")
1461         wait_for_open_port(9558)
1462         wait_until_succeeds(
1463             "curl -sSf localhost:9558/metrics | grep '{}'".format(
1464                 'systemd_unit_state{name="basic.target",state="active",type="target"} 1'
1465             )
1466         )
1467         succeed(
1468             "curl -sSf localhost:9558/metrics | grep '{}'".format(
1469                 'systemd_service_restart_total{name="prometheus-systemd-exporter.service"} 0'
1470             )
1471         )
1472       '';
1473     };
1475     tor = {
1476       exporterConfig = {
1477         enable = true;
1478       };
1479       metricProvider = {
1480         # Note: this does not connect the test environment to the Tor network.
1481         # Client, relay, bridge or exit connectivity are disabled by default.
1482         services.tor.enable = true;
1483         services.tor.settings.ControlPort = 9051;
1484       };
1485       exporterTest = ''
1486         wait_for_unit("tor.service")
1487         wait_for_open_port(9051)
1488         wait_for_unit("prometheus-tor-exporter.service")
1489         wait_for_open_port(9130)
1490         succeed("curl -sSf localhost:9130/metrics | grep 'tor_version{.\\+} 1'")
1491       '';
1492     };
1494     unpoller = {
1495       nodeName = "unpoller";
1496       exporterConfig.enable = true;
1497       exporterConfig.controllers = [{ }];
1498       exporterTest = ''
1499         wait_until_succeeds(
1500             'journalctl -eu prometheus-unpoller-exporter.service -o cat | grep "Connection Error"'
1501         )
1502       '';
1503     };
1505     unbound = {
1506       exporterConfig = {
1507         enable = true;
1508         unbound.host = "unix:///run/unbound/unbound.ctl";
1509       };
1510       metricProvider = {
1511         services.unbound = {
1512           enable = true;
1513           localControlSocketPath = "/run/unbound/unbound.ctl";
1514         };
1515         systemd.services.prometheus-unbound-exporter.serviceConfig = {
1516           SupplementaryGroups = [ "unbound" ];
1517         };
1518       };
1519       exporterTest = ''
1520         wait_for_unit("unbound.service")
1521         wait_for_unit("prometheus-unbound-exporter.service")
1522         wait_for_open_port(9167)
1523         wait_until_succeeds("curl -sSf localhost:9167/metrics | grep 'unbound_up 1'")
1524       '';
1525     };
1527     v2ray = {
1528       exporterConfig = {
1529         enable = true;
1530       };
1532       metricProvider = {
1533         systemd.services.prometheus-nginx-exporter.after = [ "v2ray.service" ];
1534         services.v2ray = {
1535           enable = true;
1536           config = {
1537             stats = {};
1538             api = {
1539               tag = "api";
1540               services = [ "StatsService" ];
1541             };
1542             inbounds = [
1543               {
1544                 port = 1080;
1545                 listen = "127.0.0.1";
1546                 protocol = "http";
1547               }
1548               {
1549                 listen = "127.0.0.1";
1550                 port = 54321;
1551                 protocol = "dokodemo-door";
1552                 settings = { address = "127.0.0.1"; };
1553                 tag = "api";
1554               }
1555             ];
1556             outbounds = [
1557               {
1558                 protocol = "freedom";
1559               }
1560               {
1561                 protocol = "freedom";
1562                 settings = {};
1563                 tag = "api";
1564               }
1565             ];
1566             routing = {
1567               strategy = "rules";
1568               settings = {
1569                 rules = [
1570                   {
1571                     inboundTag = [ "api" ];
1572                     outboundTag = "api";
1573                     type = "field";
1574                   }
1575                 ];
1576               };
1577             };
1578           };
1579         };
1580       };
1581       exporterTest = ''
1582         wait_for_unit("prometheus-v2ray-exporter.service")
1583         wait_for_open_port(9299)
1584         succeed("curl -sSf localhost:9299/scrape | grep 'v2ray_up 1'")
1585       '';
1586     };
1588     varnish = {
1589       exporterConfig = {
1590         enable = true;
1591         instance = "/var/spool/varnish/varnish";
1592         group = "varnish";
1593       };
1594       metricProvider = {
1595         systemd.services.prometheus-varnish-exporter.after = [
1596           "varnish.service"
1597         ];
1598         services.varnish = {
1599           enable = true;
1600           config = ''
1601             vcl 4.0;
1602             backend default {
1603               .host = "127.0.0.1";
1604               .port = "80";
1605             }
1606           '';
1607         };
1608       };
1609       exporterTest = ''
1610         wait_for_unit("prometheus-varnish-exporter.service")
1611         wait_for_open_port(6081)
1612         wait_for_open_port(9131)
1613         succeed("curl -sSf http://localhost:9131/metrics | grep 'varnish_up 1'")
1614       '';
1615     };
1617     wireguard = let
1618       snakeoil = import ./wireguard/snakeoil-keys.nix;
1619       publicKeyWithoutNewlines = replaceStrings [ "\n" ] [ "" ] snakeoil.peer1.publicKey;
1620     in
1621       {
1622         exporterConfig.enable = true;
1623         metricProvider = {
1624           networking.wireguard.interfaces.wg0 = {
1625             ips = [ "10.23.42.1/32" "fc00::1/128" ];
1626             listenPort = 23542;
1628             inherit (snakeoil.peer0) privateKey;
1630             peers = singleton {
1631               allowedIPs = [ "10.23.42.2/32" "fc00::2/128" ];
1633               inherit (snakeoil.peer1) publicKey;
1634             };
1635           };
1636           systemd.services.prometheus-wireguard-exporter.after = [ "wireguard-wg0.service" ];
1637         };
1638         exporterTest = ''
1639           wait_for_unit("prometheus-wireguard-exporter.service")
1640           wait_for_open_port(9586)
1641           wait_until_succeeds(
1642               "curl -sSf http://localhost:9586/metrics | grep '${publicKeyWithoutNewlines}'"
1643           )
1644         '';
1645       };
1647     zfs = {
1648       exporterConfig = {
1649         enable = true;
1650       };
1651       metricProvider = {
1652         boot.supportedFilesystems = [ "zfs" ];
1653         networking.hostId = "7327ded7";
1654       };
1655       exporterTest = ''
1656         wait_for_unit("prometheus-zfs-exporter.service")
1657         wait_for_unit("zfs.target")
1658         wait_for_open_port(9134)
1659         wait_until_succeeds("curl -f localhost:9134/metrics | grep 'zfs_scrape_collector_success{.*} 1'")
1660       '';
1661     };
1662   };
1664 mapAttrs
1665   (exporter: testConfig: (makeTest (
1666     let
1667       nodeName = testConfig.nodeName or exporter;
1669     in
1670     {
1671       name = "prometheus-${exporter}-exporter";
1673       nodes.${nodeName} = mkMerge [{
1674         services.prometheus.exporters.${exporter} = testConfig.exporterConfig;
1675       } testConfig.metricProvider or { }];
1677       testScript = ''
1678         ${nodeName}.start()
1679         ${concatStringsSep "\n" (map (line:
1680           if (builtins.substring 0 1 line == " " || builtins.substring 0 1 line == ")")
1681           then line
1682           else "${nodeName}.${line}"
1683         ) (splitString "\n" (removeSuffix "\n" testConfig.exporterTest)))}
1684         ${nodeName}.shutdown()
1685       '';
1687       meta = with maintainers; {
1688         maintainers = [ willibutz ];
1689       };
1690     }
1691   )))
1692   exporterTests