1# To run the test on the unfree ELK use the folllowing command:
2# cd path/to/nixpkgs
3# NIXPKGS_ALLOW_UNFREE=1 nix-build -A nixosTests.elk.unfree.ELK-6
4
5{ system ? builtins.currentSystem,
6 config ? {},
7 pkgs ? import ../.. { inherit system config; },
8}:
9
10let
11 inherit (pkgs) lib;
12
13 esUrl = "http://localhost:9200";
14
15 mkElkTest = name : elk :
16 import ./make-test-python.nix ({
17 inherit name;
18 meta = with pkgs.lib.maintainers; {
19 maintainers = [ eelco offline basvandijk ];
20 };
21 nodes = {
22 one =
23 { pkgs, lib, ... }: {
24 # Not giving the machine at least 2060MB results in elasticsearch failing with the following error:
25 #
26 # OpenJDK 64-Bit Server VM warning:
27 # INFO: os::commit_memory(0x0000000085330000, 2060255232, 0)
28 # failed; error='Cannot allocate memory' (errno=12)
29 #
30 # There is insufficient memory for the Java Runtime Environment to continue.
31 # Native memory allocation (mmap) failed to map 2060255232 bytes for committing reserved memory.
32 #
33 # When setting this to 2500 I got "Kernel panic - not syncing: Out of
34 # memory: compulsory panic_on_oom is enabled" so lets give it even a
35 # bit more room:
36 virtualisation.memorySize = 3000;
37
38 # For querying JSON objects returned from elasticsearch and kibana.
39 environment.systemPackages = [ pkgs.jq ];
40
41 services = {
42
43 journalbeat = let lt6 = builtins.compareVersions
44 elk.journalbeat.version "6" < 0; in {
45 enable = true;
46 package = elk.journalbeat;
47 extraConfig = pkgs.lib.mkOptionDefault (''
48 logging:
49 to_syslog: true
50 level: warning
51 metrics.enabled: false
52 output.elasticsearch:
53 hosts: [ "127.0.0.1:9200" ]
54 ${pkgs.lib.optionalString lt6 "template.enabled: false"}
55 '' + pkgs.lib.optionalString (!lt6) ''
56 journalbeat.inputs:
57 - paths: []
58 seek: cursor
59 '');
60 };
61
62 metricbeat = {
63 enable = true;
64 package = elk.metricbeat;
65 modules.system = {
66 metricsets = ["cpu" "load" "memory" "network" "process" "process_summary" "uptime" "socket_summary"];
67 enabled = true;
68 period = "5s";
69 processes = [".*"];
70 cpu.metrics = ["percentages" "normalized_percentages"];
71 core.metrics = ["percentages"];
72 };
73 settings = {
74 output.elasticsearch = {
75 hosts = ["127.0.0.1:9200"];
76 };
77 };
78 };
79
80 logstash = {
81 enable = true;
82 package = elk.logstash;
83 inputConfig = ''
84 exec { command => "echo -n flowers" interval => 1 type => "test" }
85 exec { command => "echo -n dragons" interval => 1 type => "test" }
86 '';
87 filterConfig = ''
88 if [message] =~ /dragons/ {
89 drop {}
90 }
91 '';
92 outputConfig = ''
93 file {
94 path => "/tmp/logstash.out"
95 codec => line { format => "%{message}" }
96 }
97 elasticsearch {
98 hosts => [ "${esUrl}" ]
99 }
100 '';
101 };
102
103 elasticsearch = {
104 enable = true;
105 package = elk.elasticsearch;
106 };
107
108 kibana = {
109 enable = true;
110 package = elk.kibana;
111 };
112
113 elasticsearch-curator = {
114 enable = true;
115 actionYAML = ''
116 ---
117 actions:
118 1:
119 action: delete_indices
120 description: >-
121 Delete indices older than 1 second (based on index name), for logstash-
122 prefixed indices. Ignore the error if the filter does not result in an
123 actionable list of indices (ignore_empty_list) and exit cleanly.
124 options:
125 allow_ilm_indices: true
126 ignore_empty_list: True
127 disable_action: False
128 filters:
129 - filtertype: pattern
130 kind: prefix
131 value: logstash-
132 - filtertype: age
133 source: name
134 direction: older
135 timestring: '%Y.%m.%d'
136 unit: seconds
137 unit_count: 1
138 '';
139 };
140 };
141 };
142 };
143
144 passthru.elkPackages = elk;
145 testScript = ''
146 import json
147
148
149 def total_hits(message):
150 dictionary = {"query": {"match": {"message": message}}}
151 return (
152 "curl --silent --show-error '${esUrl}/_search' "
153 + "-H 'Content-Type: application/json' "
154 + "-d '{}' ".format(json.dumps(dictionary))
155 + "| jq .hits.total"
156 )
157
158
159 def has_metricbeat():
160 dictionary = {"query": {"match": {"event.dataset": {"query": "system.cpu"}}}}
161 return (
162 "curl --silent --show-error '${esUrl}/_search' "
163 + "-H 'Content-Type: application/json' "
164 + "-d '{}' ".format(json.dumps(dictionary))
165 + "| jq '.hits.total > 0'"
166 )
167
168
169 start_all()
170
171 one.wait_for_unit("elasticsearch.service")
172 one.wait_for_open_port(9200)
173
174 # Continue as long as the status is not "red". The status is probably
175 # "yellow" instead of "green" because we are using a single elasticsearch
176 # node which elasticsearch considers risky.
177 #
178 # TODO: extend this test with multiple elasticsearch nodes
179 # and see if the status turns "green".
180 one.wait_until_succeeds(
181 "curl --silent --show-error '${esUrl}/_cluster/health' | jq .status | grep -v red"
182 )
183
184 with subtest("Perform some simple logstash tests"):
185 one.wait_for_unit("logstash.service")
186 one.wait_until_succeeds("cat /tmp/logstash.out | grep flowers")
187 one.wait_until_succeeds("cat /tmp/logstash.out | grep -v dragons")
188
189 with subtest("Kibana is healthy"):
190 one.wait_for_unit("kibana.service")
191 one.wait_until_succeeds(
192 "curl --silent --show-error 'http://localhost:5601/api/status' | jq .status.overall.state | grep green"
193 )
194
195 with subtest("Metricbeat is running"):
196 one.wait_for_unit("metricbeat.service")
197
198 with subtest("Metricbeat metrics arrive in elasticsearch"):
199 one.wait_until_succeeds(has_metricbeat() + " | tee /dev/console | grep 'true'")
200
201 with subtest("Logstash messages arive in elasticsearch"):
202 one.wait_until_succeeds(total_hits("flowers") + " | grep -v 0")
203 one.wait_until_succeeds(total_hits("dragons") + " | grep 0")
204
205 with subtest(
206 "A message logged to the journal is ingested by elasticsearch via journalbeat"
207 ):
208 one.wait_for_unit("journalbeat.service")
209 one.execute("echo 'Supercalifragilisticexpialidocious' | systemd-cat")
210 one.wait_until_succeeds(
211 total_hits("Supercalifragilisticexpialidocious") + " | grep -v 0"
212 )
213
214 with subtest("Elasticsearch-curator works"):
215 one.systemctl("stop logstash")
216 one.systemctl("start elasticsearch-curator")
217 one.wait_until_succeeds(
218 '! curl --silent --show-error "${esUrl}/_cat/indices" | grep logstash | grep ^'
219 )
220 '';
221 }) { inherit pkgs system; };
222in {
223 ELK-6 = mkElkTest "elk-6-oss" {
224 name = "elk-6-oss";
225 elasticsearch = pkgs.elasticsearch6-oss;
226 logstash = pkgs.logstash6-oss;
227 kibana = pkgs.kibana6-oss;
228 journalbeat = pkgs.journalbeat6;
229 metricbeat = pkgs.metricbeat6;
230 };
231 # We currently only package upstream binaries.
232 # Feel free to package an SSPL licensed source-based package!
233 # ELK-7 = mkElkTest "elk-7-oss" {
234 # name = "elk-7";
235 # elasticsearch = pkgs.elasticsearch7-oss;
236 # logstash = pkgs.logstash7-oss;
237 # kibana = pkgs.kibana7-oss;
238 # journalbeat = pkgs.journalbeat7;
239 # metricbeat = pkgs.metricbeat7;
240 # };
241 unfree = lib.dontRecurseIntoAttrs {
242 ELK-6 = mkElkTest "elk-6" {
243 elasticsearch = pkgs.elasticsearch6;
244 logstash = pkgs.logstash6;
245 kibana = pkgs.kibana6;
246 journalbeat = pkgs.journalbeat6;
247 metricbeat = pkgs.metricbeat6;
248 };
249 ELK-7 = mkElkTest "elk-7" {
250 elasticsearch = pkgs.elasticsearch7;
251 logstash = pkgs.logstash7;
252 kibana = pkgs.kibana7;
253 journalbeat = pkgs.journalbeat7;
254 metricbeat = pkgs.metricbeat7;
255 };
256 };
257}