2023-05-20 02:10:21 +00:00
|
|
|
# To run the test on the unfree ELK use the following command:
|
2021-11-03 11:24:41 +00:00
|
|
|
# cd path/to/nixpkgs
|
2024-01-14 11:21:45 +00:00
|
|
|
# NIXPKGS_ALLOW_UNFREE=1 nix-build -A nixosTests.elk.unfree.ELK-7
|
2021-11-03 11:24:41 +00:00
|
|
|
|
2018-11-11 08:41:11 +00:00
|
|
|
{ system ? builtins.currentSystem,
|
|
|
|
config ? {},
|
|
|
|
pkgs ? import ../.. { inherit system config; },
|
|
|
|
}:
|
|
|
|
|
2017-06-13 20:36:08 +00:00
|
|
|
let
|
2021-11-03 11:24:41 +00:00
|
|
|
inherit (pkgs) lib;
|
|
|
|
|
2017-06-13 20:36:08 +00:00
|
|
|
esUrl = "http://localhost:9200";
|
|
|
|
|
2019-04-14 19:39:46 +00:00
|
|
|
mkElkTest = name : elk :
|
2020-01-15 09:09:44 +00:00
|
|
|
import ./make-test-python.nix ({
|
2017-12-18 19:53:54 +00:00
|
|
|
inherit name;
|
2021-01-10 19:08:30 +00:00
|
|
|
meta = with pkgs.lib.maintainers; {
|
2024-04-21 07:15:22 +00:00
|
|
|
maintainers = [ offline basvandijk ];
|
2017-12-18 19:53:54 +00:00
|
|
|
};
|
|
|
|
nodes = {
|
|
|
|
one =
|
2019-05-10 13:41:41 +00:00
|
|
|
{ pkgs, lib, ... }: {
|
2017-12-18 19:53:54 +00:00
|
|
|
# Not giving the machine at least 2060MB results in elasticsearch failing with the following error:
|
|
|
|
#
|
|
|
|
# OpenJDK 64-Bit Server VM warning:
|
|
|
|
# INFO: os::commit_memory(0x0000000085330000, 2060255232, 0)
|
|
|
|
# failed; error='Cannot allocate memory' (errno=12)
|
|
|
|
#
|
|
|
|
# There is insufficient memory for the Java Runtime Environment to continue.
|
|
|
|
# Native memory allocation (mmap) failed to map 2060255232 bytes for committing reserved memory.
|
|
|
|
#
|
|
|
|
# When setting this to 2500 I got "Kernel panic - not syncing: Out of
|
|
|
|
# memory: compulsory panic_on_oom is enabled" so lets give it even a
|
|
|
|
# bit more room:
|
|
|
|
virtualisation.memorySize = 3000;
|
2017-06-13 20:36:08 +00:00
|
|
|
|
2017-12-18 19:53:54 +00:00
|
|
|
# For querying JSON objects returned from elasticsearch and kibana.
|
|
|
|
environment.systemPackages = [ pkgs.jq ];
|
2017-06-13 20:36:08 +00:00
|
|
|
|
2017-12-18 19:53:54 +00:00
|
|
|
services = {
|
2019-05-10 13:41:41 +00:00
|
|
|
|
2021-12-16 15:20:52 +00:00
|
|
|
journalbeat = {
|
|
|
|
enable = elk ? journalbeat;
|
2019-05-10 13:41:41 +00:00
|
|
|
package = elk.journalbeat;
|
2020-01-08 09:02:26 +00:00
|
|
|
extraConfig = pkgs.lib.mkOptionDefault (''
|
2019-05-10 13:41:41 +00:00
|
|
|
logging:
|
|
|
|
to_syslog: true
|
|
|
|
level: warning
|
|
|
|
metrics.enabled: false
|
|
|
|
output.elasticsearch:
|
|
|
|
hosts: [ "127.0.0.1:9200" ]
|
|
|
|
journalbeat.inputs:
|
|
|
|
- paths: []
|
|
|
|
seek: cursor
|
|
|
|
'');
|
|
|
|
};
|
|
|
|
|
2021-12-16 15:20:52 +00:00
|
|
|
filebeat = {
|
|
|
|
enable = elk ? filebeat;
|
|
|
|
package = elk.filebeat;
|
|
|
|
inputs.journald.id = "everything";
|
|
|
|
|
|
|
|
inputs.log = {
|
|
|
|
enabled = true;
|
|
|
|
paths = [
|
|
|
|
"/var/lib/filebeat/test"
|
|
|
|
];
|
|
|
|
};
|
|
|
|
|
|
|
|
settings = {
|
|
|
|
logging.level = "info";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2021-03-19 16:16:10 +00:00
|
|
|
metricbeat = {
|
|
|
|
enable = true;
|
|
|
|
package = elk.metricbeat;
|
|
|
|
modules.system = {
|
|
|
|
metricsets = ["cpu" "load" "memory" "network" "process" "process_summary" "uptime" "socket_summary"];
|
|
|
|
enabled = true;
|
|
|
|
period = "5s";
|
|
|
|
processes = [".*"];
|
|
|
|
cpu.metrics = ["percentages" "normalized_percentages"];
|
|
|
|
core.metrics = ["percentages"];
|
|
|
|
};
|
|
|
|
settings = {
|
|
|
|
output.elasticsearch = {
|
|
|
|
hosts = ["127.0.0.1:9200"];
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2017-12-18 19:53:54 +00:00
|
|
|
logstash = {
|
|
|
|
enable = true;
|
|
|
|
package = elk.logstash;
|
|
|
|
inputConfig = ''
|
|
|
|
exec { command => "echo -n flowers" interval => 1 type => "test" }
|
|
|
|
exec { command => "echo -n dragons" interval => 1 type => "test" }
|
|
|
|
'';
|
|
|
|
filterConfig = ''
|
|
|
|
if [message] =~ /dragons/ {
|
|
|
|
drop {}
|
|
|
|
}
|
|
|
|
'';
|
|
|
|
outputConfig = ''
|
|
|
|
file {
|
|
|
|
path => "/tmp/logstash.out"
|
|
|
|
codec => line { format => "%{message}" }
|
|
|
|
}
|
|
|
|
elasticsearch {
|
|
|
|
hosts => [ "${esUrl}" ]
|
|
|
|
}
|
|
|
|
'';
|
|
|
|
};
|
2017-06-13 20:36:08 +00:00
|
|
|
|
2017-12-18 19:53:54 +00:00
|
|
|
elasticsearch = {
|
|
|
|
enable = true;
|
|
|
|
package = elk.elasticsearch;
|
|
|
|
};
|
2017-06-13 20:36:08 +00:00
|
|
|
|
2018-08-21 08:39:25 +00:00
|
|
|
elasticsearch-curator = {
|
2024-01-14 11:21:45 +00:00
|
|
|
enable = elk ? elasticsearch-curator;
|
2018-08-21 08:39:25 +00:00
|
|
|
actionYAML = ''
|
|
|
|
---
|
|
|
|
actions:
|
|
|
|
1:
|
|
|
|
action: delete_indices
|
|
|
|
description: >-
|
2018-08-25 14:46:39 +00:00
|
|
|
Delete indices older than 1 second (based on index name), for logstash-
|
2018-08-21 08:39:25 +00:00
|
|
|
prefixed indices. Ignore the error if the filter does not result in an
|
|
|
|
actionable list of indices (ignore_empty_list) and exit cleanly.
|
|
|
|
options:
|
2020-04-29 13:23:50 +00:00
|
|
|
allow_ilm_indices: true
|
2018-08-21 08:39:25 +00:00
|
|
|
ignore_empty_list: True
|
|
|
|
disable_action: False
|
|
|
|
filters:
|
|
|
|
- filtertype: pattern
|
|
|
|
kind: prefix
|
|
|
|
value: logstash-
|
|
|
|
- filtertype: age
|
|
|
|
source: name
|
|
|
|
direction: older
|
|
|
|
timestring: '%Y.%m.%d'
|
2018-08-25 14:46:39 +00:00
|
|
|
unit: seconds
|
2018-08-21 08:39:25 +00:00
|
|
|
unit_count: 1
|
|
|
|
'';
|
|
|
|
};
|
2017-06-13 20:36:08 +00:00
|
|
|
};
|
|
|
|
};
|
2017-12-18 19:53:54 +00:00
|
|
|
};
|
2017-06-13 20:36:08 +00:00
|
|
|
|
2021-03-22 13:53:05 +00:00
|
|
|
passthru.elkPackages = elk;
|
2021-12-16 15:20:52 +00:00
|
|
|
testScript =
|
|
|
|
let
|
|
|
|
valueObject = lib.optionalString (lib.versionAtLeast elk.elasticsearch.version "7") ".value";
|
|
|
|
in ''
|
2020-01-08 09:02:26 +00:00
|
|
|
import json
|
|
|
|
|
|
|
|
|
2021-12-16 15:20:52 +00:00
|
|
|
def expect_hits(message):
|
|
|
|
dictionary = {"query": {"match": {"message": message}}}
|
|
|
|
return (
|
|
|
|
"curl --silent --show-error --fail-with-body '${esUrl}/_search' "
|
|
|
|
+ "-H 'Content-Type: application/json' "
|
|
|
|
+ "-d '{}' ".format(json.dumps(dictionary))
|
|
|
|
+ " | tee /dev/console"
|
|
|
|
+ " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def expect_no_hits(message):
|
2020-01-08 09:02:26 +00:00
|
|
|
dictionary = {"query": {"match": {"message": message}}}
|
|
|
|
return (
|
2021-12-16 15:20:52 +00:00
|
|
|
"curl --silent --show-error --fail-with-body '${esUrl}/_search' "
|
2020-01-08 09:02:26 +00:00
|
|
|
+ "-H 'Content-Type: application/json' "
|
|
|
|
+ "-d '{}' ".format(json.dumps(dictionary))
|
2021-12-16 15:20:52 +00:00
|
|
|
+ " | tee /dev/console"
|
|
|
|
+ " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} == 0 end'"
|
2020-01-08 09:02:26 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2021-03-19 16:16:10 +00:00
|
|
|
def has_metricbeat():
|
|
|
|
dictionary = {"query": {"match": {"event.dataset": {"query": "system.cpu"}}}}
|
|
|
|
return (
|
2021-12-16 15:20:52 +00:00
|
|
|
"curl --silent --show-error --fail-with-body '${esUrl}/_search' "
|
2021-03-19 16:16:10 +00:00
|
|
|
+ "-H 'Content-Type: application/json' "
|
|
|
|
+ "-d '{}' ".format(json.dumps(dictionary))
|
2021-12-16 15:20:52 +00:00
|
|
|
+ " | tee /dev/console"
|
|
|
|
+ " | jq -es 'if . == [] then null else .[] | .hits.total${valueObject} > 0 end'"
|
2021-03-19 16:16:10 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-01-08 09:02:26 +00:00
|
|
|
start_all()
|
2017-06-13 20:36:08 +00:00
|
|
|
|
2020-01-08 09:02:26 +00:00
|
|
|
one.wait_for_unit("elasticsearch.service")
|
|
|
|
one.wait_for_open_port(9200)
|
2017-06-13 20:36:08 +00:00
|
|
|
|
2017-12-18 19:53:54 +00:00
|
|
|
# Continue as long as the status is not "red". The status is probably
|
|
|
|
# "yellow" instead of "green" because we are using a single elasticsearch
|
|
|
|
# node which elasticsearch considers risky.
|
|
|
|
#
|
2019-05-10 13:41:41 +00:00
|
|
|
# TODO: extend this test with multiple elasticsearch nodes
|
|
|
|
# and see if the status turns "green".
|
2020-01-08 09:02:26 +00:00
|
|
|
one.wait_until_succeeds(
|
2021-12-16 15:20:52 +00:00
|
|
|
"curl --silent --show-error --fail-with-body '${esUrl}/_cluster/health'"
|
|
|
|
+ " | jq -es 'if . == [] then null else .[] | .status != \"red\" end'"
|
2020-01-08 09:02:26 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
with subtest("Perform some simple logstash tests"):
|
|
|
|
one.wait_for_unit("logstash.service")
|
|
|
|
one.wait_until_succeeds("cat /tmp/logstash.out | grep flowers")
|
|
|
|
one.wait_until_succeeds("cat /tmp/logstash.out | grep -v dragons")
|
|
|
|
|
2021-03-19 16:16:10 +00:00
|
|
|
with subtest("Metricbeat is running"):
|
|
|
|
one.wait_for_unit("metricbeat.service")
|
|
|
|
|
|
|
|
with subtest("Metricbeat metrics arrive in elasticsearch"):
|
2021-12-16 15:20:52 +00:00
|
|
|
one.wait_until_succeeds(has_metricbeat())
|
2021-03-19 16:16:10 +00:00
|
|
|
|
2020-01-08 09:02:26 +00:00
|
|
|
with subtest("Logstash messages arive in elasticsearch"):
|
2021-12-16 15:20:52 +00:00
|
|
|
one.wait_until_succeeds(expect_hits("flowers"))
|
|
|
|
one.wait_until_succeeds(expect_no_hits("dragons"))
|
2020-01-08 09:02:26 +00:00
|
|
|
|
2021-12-16 15:20:52 +00:00
|
|
|
'' + lib.optionalString (elk ? journalbeat) ''
|
2020-01-08 09:02:26 +00:00
|
|
|
with subtest(
|
|
|
|
"A message logged to the journal is ingested by elasticsearch via journalbeat"
|
|
|
|
):
|
|
|
|
one.wait_for_unit("journalbeat.service")
|
|
|
|
one.execute("echo 'Supercalifragilisticexpialidocious' | systemd-cat")
|
|
|
|
one.wait_until_succeeds(
|
2021-12-16 15:20:52 +00:00
|
|
|
expect_hits("Supercalifragilisticexpialidocious")
|
2020-01-08 09:02:26 +00:00
|
|
|
)
|
2021-12-16 15:20:52 +00:00
|
|
|
'' + lib.optionalString (elk ? filebeat) ''
|
|
|
|
with subtest(
|
|
|
|
"A message logged to the journal is ingested by elasticsearch via filebeat"
|
|
|
|
):
|
|
|
|
one.wait_for_unit("filebeat.service")
|
|
|
|
one.execute("echo 'Superdupercalifragilisticexpialidocious' | systemd-cat")
|
|
|
|
one.wait_until_succeeds(
|
|
|
|
expect_hits("Superdupercalifragilisticexpialidocious")
|
|
|
|
)
|
|
|
|
one.execute(
|
|
|
|
"echo 'SuperdupercalifragilisticexpialidociousIndeed' >> /var/lib/filebeat/test"
|
|
|
|
)
|
|
|
|
one.wait_until_succeeds(
|
|
|
|
expect_hits("SuperdupercalifragilisticexpialidociousIndeed")
|
|
|
|
)
|
2024-01-14 11:21:45 +00:00
|
|
|
'' + lib.optionalString (elk ? elasticsearch-curator) ''
|
2020-01-08 09:02:26 +00:00
|
|
|
with subtest("Elasticsearch-curator works"):
|
|
|
|
one.systemctl("stop logstash")
|
|
|
|
one.systemctl("start elasticsearch-curator")
|
|
|
|
one.wait_until_succeeds(
|
2021-12-16 15:20:52 +00:00
|
|
|
'! curl --silent --show-error --fail-with-body "${esUrl}/_cat/indices" | grep logstash | grep ^'
|
2020-01-08 09:02:26 +00:00
|
|
|
)
|
2017-12-18 19:53:54 +00:00
|
|
|
'';
|
2021-02-26 11:03:26 +00:00
|
|
|
}) { inherit pkgs system; };
|
2021-11-03 11:24:41 +00:00
|
|
|
in {
|
|
|
|
# We currently only package upstream binaries.
|
|
|
|
# Feel free to package an SSPL licensed source-based package!
|
2021-11-03 19:35:11 +00:00
|
|
|
# ELK-7 = mkElkTest "elk-7-oss" {
|
2021-11-03 11:24:41 +00:00
|
|
|
# name = "elk-7";
|
|
|
|
# elasticsearch = pkgs.elasticsearch7-oss;
|
|
|
|
# logstash = pkgs.logstash7-oss;
|
2021-12-16 15:20:52 +00:00
|
|
|
# filebeat = pkgs.filebeat7;
|
2021-11-03 11:24:41 +00:00
|
|
|
# metricbeat = pkgs.metricbeat7;
|
|
|
|
# };
|
|
|
|
unfree = lib.dontRecurseIntoAttrs {
|
|
|
|
ELK-7 = mkElkTest "elk-7" {
|
2021-11-03 10:41:53 +00:00
|
|
|
elasticsearch = pkgs.elasticsearch7;
|
|
|
|
logstash = pkgs.logstash7;
|
2021-12-16 15:20:52 +00:00
|
|
|
filebeat = pkgs.filebeat7;
|
2021-11-03 10:41:53 +00:00
|
|
|
metricbeat = pkgs.metricbeat7;
|
|
|
|
};
|
2021-11-03 11:24:41 +00:00
|
|
|
};
|
2021-11-03 10:41:53 +00:00
|
|
|
}
|