Merge ~peter-sabaini/charm-graylog:lp1758175 into charm-graylog:master

Proposed by Drew Freiberger
Status: Merged
Approved by: Xav Paice
Approved revision: adacec26185a959fc0660eca258f127469e38fc7
Merged at revision: 47d61ac0ad7465c6bae5b3198b3e95dd32c26bdf
Proposed branch: ~peter-sabaini/charm-graylog:lp1758175
Merge into: charm-graylog:master
Diff against target: 841 lines (+441/-17)
20 files modified
src/layer.yaml (+1/-0)
src/lib/charms/layer/graylog/api.py (+11/-1)
src/lib/charms/layer/graylog/constants.py (+6/-0)
src/lib/charms/layer/graylog/utils.py (+9/-2)
src/reactive/graylog.py (+72/-3)
src/templates/default-graylog-server (+4/-0)
src/templates/sync-graylog-snap (+7/-0)
src/tests/functional/requirements.txt (+2/-0)
src/tests/functional/tests/base.py (+48/-0)
src/tests/functional/tests/bundles/bionic-graylog2.yaml (+6/-0)
src/tests/functional/tests/bundles/bionic-graylog3-tls.yaml (+51/-0)
src/tests/functional/tests/bundles/bionic-graylog3.yaml (+6/-0)
src/tests/functional/tests/bundles/focal-graylog2.yaml (+6/-0)
src/tests/functional/tests/bundles/focal-graylog3-tls.yaml (+51/-0)
src/tests/functional/tests/bundles/focal-graylog3.yaml (+6/-0)
src/tests/functional/tests/test_graylog_charm.py (+12/-5)
src/tests/functional/tests/test_legacy.py (+29/-4)
src/tests/functional/tests/tests.yaml (+15/-1)
src/tests/unit/test_graylog.py (+86/-0)
src/tests/unit/test_lib.py (+13/-1)
Reviewer Review Type Date Requested Status
Xav Paice (community) Approve
Drew Freiberger (community) Approve
Paul Goins Approve
Review via email: mp+396671@code.launchpad.net

This proposal supersedes a proposal from 2021-01-21.

To post a comment you must log in.
Revision history for this message
Paul Goins (vultaire) wrote : Posted in a previous version of this proposal

This review is large, but I actually found the overall review to be good. I have no real critical feedback.

Re: the zaza bundles, something we've been adopting in other charms to reduce duplication is to have a single base bundle with all the common apps/relations ("base.yaml", not listed in tests.yaml), replace the existing bundles with symlinks to that base bundle, and then have all the per-bundle customizations moved to overlays. I don't think that's critical for this MR, but I'm mentioning it as it seems a good practice to reduce duplication and the chance of accidental divergence between bundles.

Re: code changes: the only thing I would suggest is that "make black" is run; src/tests/functional/tests/test_graylog_upgrade.py is not passing "make lint" but will simply by running "make black" and committing the changes.

I'll get this running a full test suite on my charmlab box... I'm +1 once the "make black" changes are in, assuming no failures arise during tests.

review: Needs Fixing
Revision history for this message
Paul Goins (vultaire) wrote : Posted in a previous version of this proposal

Unit tests are failing; seems more mocking is needed. "make unittests" partial output showing the failure: https://pastebin.ubuntu.com/p/M94v64H8GS/
(It's trying to do an apt-get install ca-certificates-java on my test box, which fails.)

Running functional tests now.

review: Needs Fixing
Revision history for this message
Paul Goins (vultaire) wrote : Posted in a previous version of this proposal

Functional tests pass.

If you can run "make black" and correct the unit test failure, I think this is +1 from me.

Revision history for this message
Peter Sabaini (peter-sabaini) wrote : Posted in a previous version of this proposal

I've mocked out the java CA install and blackened, please take a look?

Revision history for this message
Paul Goins (vultaire) wrote :

LGTM.

review: Approve
Revision history for this message
Drew Freiberger (afreiberger) wrote :

Expected status blocked on bionic-graylog3-tls.

Unit Workload Agent Machine Public address Ports Message
easyrsa/0* active idle 0 10.0.8.81 Certificate Authority connected.
elastic/0* active idle 1 10.0.8.91 9200/tcp Unit is ready
graylog/0* waiting idle 2 10.0.8.8 9000/tcp Waiting for: filebeat, REST API
  nrpe/0* active idle 10.0.8.8 icmp,5666/tcp ready
mongo/0* active idle 3 10.0.8.92 27017/tcp,27019/tcp,27021/tcp,28017/tcp Unit is ready
nagios/0* active idle 4 10.0.8.126 80/tcp ready
ubuntu/0* active idle 5 10.0.8.135 ready
  filebeat/0* active idle 10.0.8.135 Filebeat ready.

review: Needs Fixing
Revision history for this message
Drew Freiberger (afreiberger) wrote :

Graylog service not starting snap version is 3/stable with tls enabled:

2021-01-22T21:13:21Z systemd[1]: Started Service for snap application graylog.graylog.
2021-01-22T21:13:36Z systemd[1]: snap.graylog.graylog.service: Main process exited, code=exited, status=255/n/a
2021-01-22T21:13:36Z systemd[1]: snap.graylog.graylog.service: Failed with result 'exit-code'.

graylog traceback has this as the cause:

        Caused by: org.glassfish.grizzly.ssl.SSLContextConfigurator$GenericStoreException: java.io.FileNotFoundException: /var/snap/graylog/common/cacerts (No such file or directory)

Snap is running graylog with the following cli args: (most specifically: -Djavax.net.ssl.trustStore=/var/snap/graylog/common/cacerts)

root 7680 1 0 21:24 ? 00:00:03 /snap/graylog/17/usr/lib/jvm/java-8-openjdk-amd64/bin/java -Djavax.net.ssl.trustStore=/var/snap/graylog/common/cacerts -jar -Dlog4j.configurationFile=file:///snap/graylog/17/etc/graylog/server/log4j2.xml -Djava.library.path=/snap/graylog/17/usr/share/graylog-server/lib/sigar -Dgraylog2.installation_source=snap /snap/graylog/17/usr/share/graylog-server/graylog.jar server -f /var/snap/graylog/common/server.conf -np

Revision history for this message
Drew Freiberger (afreiberger) wrote :

After adding the sync-graylog-snap file into /etc/ca-certificates/update.d, we must run update-ca-certificates to trigger this script.

review: Needs Fixing
Revision history for this message
Drew Freiberger (afreiberger) wrote :

Getting random snap core install errors (this was on bionic-graylog3-tls model.

https://pastebin.canonical.com/p/V4Y7wTytbV/

I feel like there weren't random functest errors in 20.10, I'm running functest against that release to see results for stable/20.10 with current upstream bits and bobs to see if it's in the upstream or in our charm/functests that need repairing.

Revision history for this message
Drew Freiberger (afreiberger) wrote :

We have uncovered a number of issues with our testing infrastructure (juju not updating daily images, zaza not upgrading packages, systemd bug affecting ES patched a couple weeks ago) that are causing random failures of the functional tests, but 30% pass w/out issue. I suggest merging this and opening a bug report for racy tests.

review: Approve
Revision history for this message
Xav Paice (xavpaice) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/layer.yaml b/src/layer.yaml
index 04e6d2a..5173d7e 100644
--- a/src/layer.yaml
+++ b/src/layer.yaml
@@ -3,6 +3,7 @@ includes:
3 - 'layer:basic'3 - 'layer:basic'
4 - 'layer:snap'4 - 'layer:snap'
5 - 'layer:leadership'5 - 'layer:leadership'
6 - 'layer:tls-client'
6 - 'interface:elasticsearch'7 - 'interface:elasticsearch'
7 - 'interface:elastic-beats'8 - 'interface:elastic-beats'
8 - 'interface:http'9 - 'interface:http'
diff --git a/src/lib/charms/layer/graylog/api.py b/src/lib/charms/layer/graylog/api.py
index 98e52cf..3d9bf93 100644
--- a/src/lib/charms/layer/graylog/api.py
+++ b/src/lib/charms/layer/graylog/api.py
@@ -4,6 +4,12 @@ import os
44
5import requests5import requests
66
7# When using 'certifi' from the virtualenv, the system-wide certificates store
8# is not used, so installed certificates won't be used to validate hosts.
9# Adding the system CA bundle
10# https://git.launchpad.net/ubuntu/+source/python-certifi/tree/debian/patches/0001-Use-Debian-provided-etc-ssl-certs-ca-certificates.cr.patch
11SYSTEM_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"
12
7# We are in a charm environment13# We are in a charm environment
8charm = False14charm = False
9if os.environ.get("JUJU_UNIT_NAME"):15if os.environ.get("JUJU_UNIT_NAME"):
@@ -20,7 +26,9 @@ def get_ignore_indexer_failures_file(): # noqa: D103
20class GraylogApi:26class GraylogApi:
21 """Manage Graylog via its API."""27 """Manage Graylog via its API."""
2228
23 def __init__(self, base_url, username, password, token_name="graylog-api"):29 def __init__(
30 self, base_url, username, password, token_name="graylog-api", verify=None
31 ):
24 """Initialize HTTP values."""32 """Initialize HTTP values."""
25 self.base_url = base_url33 self.base_url = base_url
26 if not base_url.endswith("/"):34 if not base_url.endswith("/"):
@@ -33,6 +41,7 @@ class GraylogApi:
33 self.input_types = None41 self.input_types = None
34 self.req_timeout = 342 self.req_timeout = 3
35 self.req_retries = 443 self.req_retries = 4
44 self.verify = verify
3645
37 def request(self, path, method="GET", data={}, params=None): # noqa: C90146 def request(self, path, method="GET", data={}, params=None): # noqa: C901
38 """Retrieve data by URL."""47 """Retrieve data by URL."""
@@ -59,6 +68,7 @@ class GraylogApi:
59 params=params,68 params=params,
60 headers=headers,69 headers=headers,
61 timeout=self.req_timeout,70 timeout=self.req_timeout,
71 verify=SYSTEM_CA_BUNDLE if self.verify is None else self.verify,
62 )72 )
63 if resp.ok:73 if resp.ok:
64 if method == "DELETE":74 if method == "DELETE":
diff --git a/src/lib/charms/layer/graylog/constants.py b/src/lib/charms/layer/graylog/constants.py
index db38aa0..ce2bc59 100644
--- a/src/lib/charms/layer/graylog/constants.py
+++ b/src/lib/charms/layer/graylog/constants.py
@@ -1,5 +1,9 @@
1"""Options used by the Graylog charm."""1"""Options used by the Graylog charm."""
2import os
3
4
2SNAP_NAME = "graylog"5SNAP_NAME = "graylog"
6SNAP_COMMON_DIR = "/var/snap/graylog/common/"
3CONF_FILE = "/var/snap/graylog/common/server.conf"7CONF_FILE = "/var/snap/graylog/common/server.conf"
4SNAP_CONF_FILE = "/snap/graylog/current/etc/graylog/server/server.conf"8SNAP_CONF_FILE = "/snap/graylog/current/etc/graylog/server/server.conf"
5# /snap/graylog is a read-only squashfs so we use the initial settings9# /snap/graylog is a read-only squashfs so we use the initial settings
@@ -12,3 +16,5 @@ ELASTICSEARCH_DISCOVERY_PORT = "9300"
12SERVICE_NAME = "snap.graylog.graylog"16SERVICE_NAME = "snap.graylog.graylog"
13DEFAULT_REST_API_TIMEOUT = 12017DEFAULT_REST_API_TIMEOUT = 120
14NAGIOS_USERNAME = "nagios"18NAGIOS_USERNAME = "nagios"
19CERT_PATH = os.path.join(SNAP_COMMON_DIR, "server.crt")
20CERT_KEY_PATH = os.path.join(SNAP_COMMON_DIR, "server.key")
diff --git a/src/lib/charms/layer/graylog/utils.py b/src/lib/charms/layer/graylog/utils.py
index 115e672..6b020c5 100644
--- a/src/lib/charms/layer/graylog/utils.py
+++ b/src/lib/charms/layer/graylog/utils.py
@@ -4,10 +4,10 @@ from urllib.parse import urlparse
4from charmhelpers.core import hookenv4from charmhelpers.core import hookenv
55
6from charms.layer.graylog.constants import SNAP_NAME6from charms.layer.graylog.constants import SNAP_NAME
7from charms.reactive import get_flags
78
8import netifaces9import netifaces
910
10
11# Allow mocking of other layers during unit tests11# Allow mocking of other layers during unit tests
12try:12try:
13 from charms.layer import snap13 from charms.layer import snap
@@ -20,7 +20,14 @@ def get_api_port(): # noqa: D103
2020
2121
22def get_api_url(): # noqa: D10322def get_api_url(): # noqa: D103
23 return "http://127.0.0.1:{}/api/".format(get_api_port())23 return "{}://127.0.0.1:{}/api/".format(get_api_protocol(), get_api_port())
24
25
26def get_api_protocol(): # noqa: D103
27 if "graylog.certificates.configured" in get_flags():
28 return "https"
29 else:
30 return "http"
2431
2532
26def is_v2(): # noqa: D10333def is_v2(): # noqa: D103
diff --git a/src/reactive/graylog.py b/src/reactive/graylog.py
index 9cc3a93..4f4a9ee 100644
--- a/src/reactive/graylog.py
+++ b/src/reactive/graylog.py
@@ -2,23 +2,29 @@
2import hashlib2import hashlib
3import os3import os
4import re4import re
5import socket
6import subprocess
5import time7import time
6from urllib.parse import urlparse8from urllib.parse import urlparse
79
8from charmhelpers.contrib.charmsupport import nrpe10from charmhelpers.contrib.charmsupport import nrpe
9from charmhelpers.core import hookenv, host, unitdata11from charmhelpers.core import hookenv, host, templating, unitdata
12from charmhelpers.fetch import filter_installed_packages, install
1013
11import charms.leadership14import charms.leadership
12from charms.layer import snap15from charms.layer import snap, tls_client
13from charms.layer.graylog import (16from charms.layer.graylog import (
14 GraylogApi,17 GraylogApi,
15 LogExtractPipeline,18 LogExtractPipeline,
16 get_api_port,19 get_api_port,
20 get_api_protocol,
17 get_api_url,21 get_api_url,
18 is_v2,22 is_v2,
19 validate_api_uri,23 validate_api_uri,
20)24)
21from charms.layer.graylog.constants import (25from charms.layer.graylog.constants import (
26 CERT_KEY_PATH,
27 CERT_PATH,
22 CONF_FILE,28 CONF_FILE,
23 DEFAULT_REST_API_TIMEOUT,29 DEFAULT_REST_API_TIMEOUT,
24 ELASTICSEARCH_DISCOVERY_PORT,30 ELASTICSEARCH_DISCOVERY_PORT,
@@ -200,7 +206,8 @@ def configure_graylog(): # noqa: C901
200 # intead of guessing the best IP.206 # intead of guessing the best IP.
201 set_conf("http_bind_address", "0.0.0.0:9000")207 set_conf("http_bind_address", "0.0.0.0:9000")
202 set_conf(208 set_conf(
203 "http_publish_uri", validate_api_uri("http://0.0.0.0:{}/".format(api_port))209 "http_publish_uri",
210 validate_api_uri("{}://0.0.0.0:{}/".format(get_api_protocol(), api_port)),
204 )211 )
205212
206 if conf["web_endpoint_uri"]:213 if conf["web_endpoint_uri"]:
@@ -1045,6 +1052,49 @@ def configure_nagios(nagios):
1045 set_state("graylog_nagios.configured")1052 set_state("graylog_nagios.configured")
10461053
10471054
1055@when("certificates.available")
1056@when_not("graylog.certificates.configured")
1057def tls_request_certificate(tls):
1058 """Create a server certificate for this server."""
1059 # ca-certificates-java generates the ca-certificates in a jvm compatible
1060 # keystore
1061 if filter_installed_packages(["ca-certificates-java"]):
1062 install(["ca-certificates-java"], fatal=True)
1063
1064 _maybe_install_ca_certificates_hook()
1065 _maybe_configure_graylog_jvm_keystore()
1066
1067 # Use the public ip of this unit as the Common Name for the certificate.
1068 common_name = hookenv.unit_public_ip()
1069 # Get a list of Subject Alt Names for the certificate.
1070 sans = []
1071 sans.append(hookenv.unit_public_ip())
1072 sans.append(hookenv.unit_private_ip())
1073 sans.append(socket.gethostname())
1074 sans.append("127.0.0.1")
1075 sans.append("localhost")
1076 tls_client.request_server_cert(
1077 common_name, sans, crt_path=CERT_PATH, key_path=CERT_KEY_PATH
1078 )
1079
1080 set_conf("http_enable_tls", "true")
1081 set_conf("http_tls_cert_file", CERT_PATH)
1082 set_conf("http_tls_key_file", CERT_KEY_PATH)
1083
1084 set_conf("rest_enable_tls", "true")
1085 set_conf("rest_tls_cert_file", CERT_PATH)
1086 set_conf("rest_tls_key_file", CERT_KEY_PATH)
1087
1088 set_conf("web_enable_tls", "true")
1089 set_conf("web_tls_cert_file", CERT_PATH)
1090 set_conf("web_tls_key_file", CERT_KEY_PATH)
1091
1092 set_conf("http_publish_uri", "https://0.0.0.0:{}/".format(get_api_port()))
1093
1094 set_state("graylog.certificates.configured")
1095 set_state("graylog.needs_restart")
1096
1097
1048def get_default_graylog_client(): # noqa: D1031098def get_default_graylog_client(): # noqa: D103
1049 db = unitdata.kv()1099 db = unitdata.kv()
1050 return GraylogApi(1100 return GraylogApi(
@@ -1068,3 +1118,22 @@ def _verify_rest_api_is_alive(timeout=DEFAULT_REST_API_TIMEOUT):
1068 if time.time() - start_ts > timeout:1118 if time.time() - start_ts > timeout:
1069 raise ApiTimeout()1119 raise ApiTimeout()
1070 hookenv.log("REST API is up")1120 hookenv.log("REST API is up")
1121
1122
1123def _maybe_install_ca_certificates_hook():
1124 templating.render(
1125 "sync-graylog-snap",
1126 "/etc/ca-certificates/update.d/sync-graylog-snap",
1127 context={},
1128 perms=0o755,
1129 )
1130 subprocess.check_call(["update-ca-certificates"])
1131
1132
1133def _maybe_configure_graylog_jvm_keystore():
1134 templating.render(
1135 "default-graylog-server",
1136 "/var/snap/graylog/current/default-graylog-server",
1137 context={},
1138 perms=0o644,
1139 )
diff --git a/src/templates/default-graylog-server b/src/templates/default-graylog-server
1071new file mode 1006441140new file mode 100644
index 0000000..ace24cb
--- /dev/null
+++ b/src/templates/default-graylog-server
@@ -0,0 +1,4 @@
1#
2# This file is managed by juju.
3#
4GRAYLOG_SERVER_JAVA_OPTS="-Djavax.net.ssl.trustStore=/var/snap/graylog/common/cacerts"
diff --git a/src/templates/sync-graylog-snap b/src/templates/sync-graylog-snap
0new file mode 1007555new file mode 100755
index 0000000..6d08b7d
--- /dev/null
+++ b/src/templates/sync-graylog-snap
@@ -0,0 +1,7 @@
1#!/bin/sh
2#
3# This file is managed by juju.
4#
5
6set -e
7rsync /etc/ssl/certs/java/cacerts /var/snap/graylog/common/cacerts
diff --git a/src/tests/functional/requirements.txt b/src/tests/functional/requirements.txt
index 7345526..ecf87be 100644
--- a/src/tests/functional/requirements.txt
+++ b/src/tests/functional/requirements.txt
@@ -1,2 +1,4 @@
1tenacity1tenacity
2git+https://github.com/openstack-charmers/zaza.git#egg=zaza2git+https://github.com/openstack-charmers/zaza.git#egg=zaza
3netifaces
4charmhelpers
diff --git a/src/tests/functional/tests/base.py b/src/tests/functional/tests/base.py
3new file mode 1006445new file mode 100644
index 0000000..46eb75f
--- /dev/null
+++ b/src/tests/functional/tests/base.py
@@ -0,0 +1,48 @@
1"""Base class for Testing."""
2import logging
3import os
4import tempfile
5import unittest
6
7from zaza import model
8
9
10class BaseTestCase(unittest.TestCase):
11 """Base class for graylog functional testing."""
12
13 @classmethod
14 def setUpClass(cls):
15 """Configure the test's environment.
16
17 - Get the CA from easyrsa (when available) and write it on disk.
18 """
19 if cls.get_api_protocol() == "https":
20 rel_id = model.get_relation_id(
21 "graylog", "easyrsa", remote_interface_name="client"
22 )
23 easyrsa_unit = model.get_units("easyrsa")[0]
24 cmd = "relation-get -r client:{} ca {}".format(
25 rel_id, easyrsa_unit.entity_id
26 )
27 logging.info(cmd)
28 result = model.run_on_unit(easyrsa_unit.entity_id, cmd)
29
30 with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
31 f.write(result["Stdout"])
32 f.flush()
33 cls.ca_path = f.name
34 else:
35 cls.ca_path = None
36
37 @classmethod
38 def tearDownClass(cls):
39 """Remove the CA file that was written to disk during the setUp."""
40 if cls.ca_path:
41 os.remove(cls.ca_path)
42
43 @classmethod
44 def get_api_protocol(cls): # noqa: D102
45 if model.get_relation_id("graylog", "easyrsa"):
46 return "https"
47 else:
48 return "http"
diff --git a/src/tests/functional/tests/bundles/bionic-graylog2.yaml b/src/tests/functional/tests/bundles/bionic-graylog2.yaml
index bd256db..27703fd 100644
--- a/src/tests/functional/tests/bundles/bionic-graylog2.yaml
+++ b/src/tests/functional/tests/bundles/bionic-graylog2.yaml
@@ -30,6 +30,10 @@ applications:
30 nrpe:30 nrpe:
31 charm: cs:nrpe31 charm: cs:nrpe
3232
33 nagios:
34 charm: cs:nagios
35 num_units: 1
36
33relations:37relations:
34 - - ubuntu38 - - ubuntu
35 - filebeat39 - filebeat
@@ -43,3 +47,5 @@ relations:
43 - haproxy47 - haproxy
44 - - graylog48 - - graylog
45 - nrpe49 - nrpe
50 - - nagios
51 - nrpe
diff --git a/src/tests/functional/tests/bundles/bionic-graylog3-tls.yaml b/src/tests/functional/tests/bundles/bionic-graylog3-tls.yaml
46new file mode 10064452new file mode 100644
index 0000000..954f9c5
--- /dev/null
+++ b/src/tests/functional/tests/bundles/bionic-graylog3-tls.yaml
@@ -0,0 +1,51 @@
1series: bionic
2
3applications:
4 ubuntu:
5 charm: cs:ubuntu
6 num_units: 1
7
8 filebeat:
9 charm: cs:filebeat
10 num_units: 0
11
12 graylog:
13 num_units: 1
14 series: bionic
15 options:
16 channel: 3/stable
17
18 elastic:
19 charm: cs:elasticsearch
20 num_units: 1
21
22 mongo:
23 charm: cs:mongodb
24 num_units: 1
25
26 nrpe:
27 charm: cs:nrpe
28
29 nagios:
30 charm: cs:nagios
31 num_units: 1
32
33 easyrsa:
34 charm: cs:~containers/easyrsa
35 num_units: 1
36
37relations:
38 - - ubuntu
39 - filebeat
40 - - graylog:beats
41 - filebeat:logstash
42 - - graylog
43 - mongo
44 - - graylog
45 - elastic
46 - - graylog
47 - nrpe
48 - - nagios
49 - nrpe
50 - - easyrsa:client
51 - graylog:certificates
diff --git a/src/tests/functional/tests/bundles/bionic-graylog3.yaml b/src/tests/functional/tests/bundles/bionic-graylog3.yaml
index 596cf7d..33ac7e5 100644
--- a/src/tests/functional/tests/bundles/bionic-graylog3.yaml
+++ b/src/tests/functional/tests/bundles/bionic-graylog3.yaml
@@ -30,6 +30,10 @@ applications:
30 nrpe:30 nrpe:
31 charm: cs:nrpe31 charm: cs:nrpe
3232
33 nagios:
34 charm: cs:nagios
35 num_units: 1
36
33relations:37relations:
34 - - ubuntu38 - - ubuntu
35 - filebeat39 - filebeat
@@ -43,3 +47,5 @@ relations:
43 - haproxy47 - haproxy
44 - - graylog48 - - graylog
45 - nrpe49 - nrpe
50 - - nagios
51 - nrpe
diff --git a/src/tests/functional/tests/bundles/focal-graylog2.yaml b/src/tests/functional/tests/bundles/focal-graylog2.yaml
index 368432e..9cc6016 100644
--- a/src/tests/functional/tests/bundles/focal-graylog2.yaml
+++ b/src/tests/functional/tests/bundles/focal-graylog2.yaml
@@ -30,6 +30,10 @@ applications:
30 nrpe:30 nrpe:
31 charm: cs:nrpe31 charm: cs:nrpe
3232
33 nagios:
34 charm: cs:nagios
35 num_units: 1
36
33relations:37relations:
34 - - ubuntu38 - - ubuntu
35 - filebeat39 - filebeat
@@ -43,3 +47,5 @@ relations:
43 - haproxy47 - haproxy
44 - - graylog48 - - graylog
45 - nrpe49 - nrpe
50 - - nagios
51 - nrpe
diff --git a/src/tests/functional/tests/bundles/focal-graylog3-tls.yaml b/src/tests/functional/tests/bundles/focal-graylog3-tls.yaml
46new file mode 10064452new file mode 100644
index 0000000..100ccb4
--- /dev/null
+++ b/src/tests/functional/tests/bundles/focal-graylog3-tls.yaml
@@ -0,0 +1,51 @@
1series: bionic
2
3applications:
4 ubuntu:
5 charm: cs:ubuntu
6 num_units: 1
7
8 filebeat:
9 charm: cs:filebeat
10 num_units: 0
11
12 graylog:
13 num_units: 1
14 series: focal
15 options:
16 channel: 3/stable
17
18 elastic:
19 charm: cs:elasticsearch
20 num_units: 1
21
22 mongo:
23 charm: cs:mongodb
24 num_units: 1
25
26 nrpe:
27 charm: cs:nrpe
28
29 nagios:
30 charm: cs:nagios
31 num_units: 1
32
33 easyrsa:
34 charm: cs:~containers/easyrsa
35 num_units: 1
36
37relations:
38 - - ubuntu
39 - filebeat
40 - - graylog:beats
41 - filebeat:logstash
42 - - graylog
43 - mongo
44 - - graylog
45 - elastic
46 - - graylog
47 - nrpe
48 - - nagios
49 - nrpe
50 - - easyrsa:client
51 - graylog:certificates
diff --git a/src/tests/functional/tests/bundles/focal-graylog3.yaml b/src/tests/functional/tests/bundles/focal-graylog3.yaml
index cb26f52..e2e2a41 100644
--- a/src/tests/functional/tests/bundles/focal-graylog3.yaml
+++ b/src/tests/functional/tests/bundles/focal-graylog3.yaml
@@ -30,6 +30,10 @@ applications:
30 nrpe:30 nrpe:
31 charm: cs:nrpe31 charm: cs:nrpe
3232
33 nagios:
34 charm: cs:nagios
35 num_units: 1
36
33relations:37relations:
34 - - ubuntu38 - - ubuntu
35 - filebeat39 - filebeat
@@ -43,3 +47,5 @@ relations:
43 - haproxy47 - haproxy
44 - - graylog48 - - graylog
45 - nrpe49 - nrpe
50 - - nagios
51 - nrpe
diff --git a/src/tests/functional/tests/test_graylog_charm.py b/src/tests/functional/tests/test_graylog_charm.py
index df763b6..83d58e7 100644
--- a/src/tests/functional/tests/test_graylog_charm.py
+++ b/src/tests/functional/tests/test_graylog_charm.py
@@ -2,12 +2,13 @@
22
3import logging3import logging
4import time4import time
5import unittest
65
7from api import GraylogApi6from api import GraylogApi
87
9import tenacity8import tenacity
109
10from tests.base import BaseTestCase
11
11import zaza.model as model12import zaza.model as model
1213
1314
@@ -17,7 +18,7 @@ DEFAULT_API_PORT = "9001"
17DEFAULT_WEB_PORT = "9000"18DEFAULT_WEB_PORT = "9000"
1819
1920
20class BaseGraylogTest(unittest.TestCase):21class BaseGraylogTest(BaseTestCase):
21 """Base for Graylog charm tests."""22 """Base for Graylog charm tests."""
2223
23 @classmethod24 @classmethod
@@ -56,10 +57,14 @@ class BaseGraylogTest(unittest.TestCase):
56 "org.graylog.plugins.threatintel.ThreatIntelPlugin",57 "org.graylog.plugins.threatintel.ThreatIntelPlugin",
57 }58 }
5859
59 api_url = "http://{}:{}/api".format(cls.graylog_ip, cls.api_port)60 api_url = "{}://{}:{}/api".format(
61 cls.get_api_protocol(), cls.graylog_ip, cls.api_port
62 )
60 action = model.run_action_on_leader(cls.application_name, "show-admin-password")63 action = model.run_action_on_leader(cls.application_name, "show-admin-password")
61 res = action.data["results"]64 res = action.data["results"]
62 cls.api = GraylogApi(api_url, "admin", res["admin-password"])65 cls.api = GraylogApi(
66 api_url, "admin", res["admin-password"], verify=cls.ca_path
67 )
63 # try harder to get an api connection to cater for overloaded testsystems68 # try harder to get an api connection to cater for overloaded testsystems
64 cls.api.req_timeout = REQ_TIMEOUT69 cls.api.req_timeout = REQ_TIMEOUT
65 logging.debug("API at {}".format(api_url))70 logging.debug("API at {}".format(api_url))
@@ -75,7 +80,9 @@ class CharmOperationTest(BaseGraylogTest):
75 until the CURL_TIMEOUT because it may take a few seconds for the80 until the CURL_TIMEOUT because it may take a few seconds for the
76 graylog systemd service to start.81 graylog systemd service to start.
77 """82 """
78 curl_command = "curl http://localhost:{}".format(self.api_port)83 curl_command = "curl {}://localhost:{}".format(
84 self.get_api_protocol(), self.api_port
85 )
79 timeout = time.time() + CURL_TIMEOUT86 timeout = time.time() + CURL_TIMEOUT
80 while time.time() < timeout:87 while time.time() < timeout:
81 response = model.run_on_unit(self.lead_unit_name, curl_command)88 response = model.run_on_unit(self.lead_unit_name, curl_command)
diff --git a/src/tests/functional/tests/test_legacy.py b/src/tests/functional/tests/test_legacy.py
index 5ee2eea..fcea119 100644
--- a/src/tests/functional/tests/test_legacy.py
+++ b/src/tests/functional/tests/test_legacy.py
@@ -1,9 +1,14 @@
1"""Graylog v2 legacy tests."""1"""Graylog v2 legacy tests."""
2import logging
2import re3import re
3import unittest4import unittest
45
5from api import GraylogApi6from api import GraylogApi
67
8from charmhelpers.core.decorators import retry_on_exception
9
10from tests.base import BaseTestCase
11
7import yaml12import yaml
813
9from zaza import model14from zaza import model
@@ -14,7 +19,7 @@ DEFAULT_API_PORT = "9001" # Graylog 2 only
14DEFAULT_WEB_PORT = "9000"19DEFAULT_WEB_PORT = "9000"
1520
1621
17class LegacyTests(unittest.TestCase):22class LegacyTests(BaseTestCase):
18 """These tests were ported from tests/test_10_basic.py in changeset a3b54c2.23 """These tests were ported from tests/test_10_basic.py in changeset a3b54c2.
1924
20 They were temporarily deleted during the conversion from Amulet to Zaza.25 They were temporarily deleted during the conversion from Amulet to Zaza.
@@ -24,8 +29,12 @@ class LegacyTests(unittest.TestCase):
2429
25 def test_api_ready(self):30 def test_api_ready(self):
26 """Curl the api endpoint on the graylog unit."""31 """Curl the api endpoint on the graylog unit."""
32 protocol = self.get_api_protocol()
27 port = self.get_api_port()33 port = self.get_api_port()
28 curl_command = "curl http://localhost:{} --connect-timeout 30".format(port)34 curl_command = ("curl {}://localhost:{} " "--connect-timeout 30").format(
35 protocol, port
36 )
37 logging.info(curl_command)
29 self.run_command("graylog/0", curl_command)38 self.run_command("graylog/0", curl_command)
3039
31 def test_elasticsearch_active(self):40 def test_elasticsearch_active(self):
@@ -60,10 +69,16 @@ class LegacyTests(unittest.TestCase):
60 """Return the list of API clients to Graylog units."""69 """Return the list of API clients to Graylog units."""
61 graylog_units = juju.get_full_juju_status().applications["graylog"]["units"]70 graylog_units = juju.get_full_juju_status().applications["graylog"]["units"]
62 graylog_addrs = [unit["public-address"] for unit in graylog_units.values()]71 graylog_addrs = [unit["public-address"] for unit in graylog_units.values()]
72 protocol = self.get_api_protocol()
63 port = self.get_api_port()73 port = self.get_api_port()
64 password = self.get_graylog_admin_password()74 password = self.get_graylog_admin_password()
65 return [75 return [
66 GraylogApi("http://{}:{}/api".format(graylog_addr, port), "admin", password)76 GraylogApi(
77 "{}://{}:{}/api".format(protocol, graylog_addr, port),
78 "admin",
79 password,
80 verify=self.ca_path,
81 )
67 for graylog_addr in graylog_addrs82 for graylog_addr in graylog_addrs
68 ]83 ]
6984
@@ -76,8 +91,17 @@ class LegacyTests(unittest.TestCase):
76 password = result.data["results"]["admin-password"]91 password = result.data["results"]["admin-password"]
77 return password92 return password
7893
79 def test_website_active(self):94 def test_website_active_with_haproxy(self):
80 """Verify if the Graylog endpoints are correctly configured."""95 """Verify if the Graylog endpoints are correctly configured."""
96 try:
97 model.get_application("haproxy")
98 except KeyError:
99 reason = (
100 "haproxy is not in the model, assuming there is "
101 "no front loadbalancer"
102 )
103 raise unittest.SkipTest(reason)
104
81 data = juju.get_relation_from_unit("haproxy/0", "graylog/0", "website")105 data = juju.get_relation_from_unit("haproxy/0", "graylog/0", "website")
82 self.assertEqual(data["port"], DEFAULT_WEB_PORT)106 self.assertEqual(data["port"], DEFAULT_WEB_PORT)
83107
@@ -117,6 +141,7 @@ class LegacyTests(unittest.TestCase):
117 )141 )
118 self.assertTrue(re.search(r"CRITICAL", cfg))142 self.assertTrue(re.search(r"CRITICAL", cfg))
119143
144 @retry_on_exception(num_retries=5, base_delay=2, exc_type=AssertionError)
120 def get_file_contents(self, unit, filename):145 def get_file_contents(self, unit, filename):
121 """Retrieve content of a file in a remote unit."""146 """Retrieve content of a file in a remote unit."""
122 result = self.run_command(unit, "cat '{}'".format(filename))147 result = self.run_command(unit, "cat '{}'".format(filename))
diff --git a/src/tests/functional/tests/tests.yaml b/src/tests/functional/tests/tests.yaml
index c52c200..47db931 100644
--- a/src/tests/functional/tests/tests.yaml
+++ b/src/tests/functional/tests/tests.yaml
@@ -1,9 +1,11 @@
1charm_name: graylog1charm_name: graylog
2gate_bundles:2gate_bundles:
3 - gl2: bionic-graylog23 - gl2: bionic-graylog2
4 - gl3: bionic-graylog3
5 - gl2: focal-graylog24 - gl2: focal-graylog2
5 - gl3: bionic-graylog3
6 - gl3: bionic-graylog3-tls
6 - gl3: focal-graylog37 - gl3: focal-graylog3
8 - gl3: focal-graylog3-tls
7# - gl2: bionic-graylog2-ha9# - gl2: bionic-graylog2-ha
8# - gl3: bionic-graylog3-ha10# - gl3: bionic-graylog3-ha
9smoke_bundles:11smoke_bundles:
@@ -28,3 +30,15 @@ target_deploy_status:
28 nrpe:30 nrpe:
29 workload-status: blocked31 workload-status: blocked
30 workload-status-message: Nagios server not configured or related32 workload-status-message: Nagios server not configured or related
33 mongo:
34 workload-status: active
35 workload-status-message: 'Unit is ready'
36 graylog:
37 workload-status: active
38 workload-status-message: 'Ready with: filebeat, elasticsearch, mongodb'
39 elastic:
40 workload-status: active
41 workload-status-message: 'Unit is ready'
42 easyrsa:
43 workload-status-message: 'Certificate Authority connected.'
44
diff --git a/src/tests/unit/test_graylog.py b/src/tests/unit/test_graylog.py
index c723ce1..a9b0ae8 100644
--- a/src/tests/unit/test_graylog.py
+++ b/src/tests/unit/test_graylog.py
@@ -1,10 +1,15 @@
1"""Tests around the graylog reactive script."""1"""Tests around the graylog reactive script."""
2import os2import os
3import socket
3import sys4import sys
4import tempfile5import tempfile
5import unittest6import unittest
6from unittest import mock7from unittest import mock
78
9from charms.layer.graylog.constants import (
10 CERT_KEY_PATH,
11 CERT_PATH,
12)
8from charms.layer.graylog.snap_change import (13from charms.layer.graylog.snap_change import (
9 ChannelChangeStatus,14 ChannelChangeStatus,
10 _is_channel_valid,15 _is_channel_valid,
@@ -17,6 +22,8 @@ leader_mock = mock.Mock()
17sys.modules["charms.leadership"] = leader_mock22sys.modules["charms.leadership"] = leader_mock
18snap_mock = mock.Mock()23snap_mock = mock.Mock()
19sys.modules["charms.layer.snap"] = snap_mock24sys.modules["charms.layer.snap"] = snap_mock
25tls_client_mock = mock.Mock()
26sys.modules["charms.layer.tls_client"] = tls_client_mock
2027
21from files import check_graylog_health # noqa: E40228from files import check_graylog_health # noqa: E402
2229
@@ -28,6 +35,7 @@ from reactive.graylog import ( # noqa: E402
28 refresh_graylog,35 refresh_graylog,
29 set_conf,36 set_conf,
30 set_jvm_heap_size,37 set_jvm_heap_size,
38 tls_request_certificate,
31 update_config,39 update_config,
32 upgrade_charm,40 upgrade_charm,
33)41)
@@ -555,3 +563,81 @@ class TestIsChannelValid(unittest.TestCase):
555 "2", # track w/o risk563 "2", # track w/o risk
556 ):564 ):
557 self.assertFalse(_is_channel_valid(channel))565 self.assertFalse(_is_channel_valid(channel))
566
567
568class TestTlsClient(unittest.TestCase):
569 """Test TLS Client integration."""
570
571 @mock.patch("charmhelpers.core.host.mkdir")
572 @mock.patch("charmhelpers.core.host.write_file")
573 @mock.patch("charmhelpers.core.hookenv.charm_dir")
574 @mock.patch("charms.layer.graylog.utils.is_v2")
575 @mock.patch("reactive.graylog.set_conf")
576 @mock.patch("reactive.graylog.hookenv.unit_public_ip")
577 @mock.patch("reactive.graylog.hookenv.unit_private_ip")
578 @mock.patch("reactive.graylog.install")
579 @mock.patch("subprocess.check_call")
580 def test_request_certificate(
581 self,
582 mock_check_call,
583 mock_install,
584 mock_private_ip,
585 mock_public_ip,
586 mock_set_conf,
587 mock_is_v2,
588 mock_charm_dir,
589 mock_write_file,
590 mock_mkdir,
591 ): # noqa: D102
592 charm_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")
593 mock_charm_dir.return_value = charm_dir
594 mock_is_v2.return_value = False
595 mock_public_ip.return_value = "1.2.3.4"
596 mock_private_ip.return_value = "5.6.7.8"
597 mock_tls = mock.Mock()
598 tls_request_certificate(mock_tls)
599
600 tls_client_mock.request_server_cert.assert_called_with(
601 "1.2.3.4",
602 ["1.2.3.4", "5.6.7.8", socket.gethostname(), "127.0.0.1", "localhost"],
603 crt_path=CERT_PATH,
604 key_path=CERT_KEY_PATH,
605 )
606 mock_set_conf.assert_has_calls(
607 [
608 mock.call("rest_enable_tls", "true"),
609 mock.call("rest_tls_cert_file", CERT_PATH),
610 mock.call("rest_tls_key_file", CERT_KEY_PATH),
611 mock.call("web_enable_tls", "true"),
612 mock.call("web_tls_cert_file", CERT_PATH),
613 mock.call("web_tls_key_file", CERT_KEY_PATH),
614 ]
615 )
616
617 with open(os.path.join(charm_dir, "templates/sync-graylog-snap"), "rb") as f:
618 sync_script = f.read().strip()
619
620 with open(
621 os.path.join(charm_dir, "templates/default-graylog-server"), "rb"
622 ) as f:
623 default_graylog_server = f.read().strip()
624
625 mock_write_file.assert_has_calls(
626 [
627 mock.call(
628 "/etc/ca-certificates/update.d/sync-graylog-snap",
629 sync_script,
630 "root",
631 "root",
632 0o755,
633 ),
634 mock.call(
635 "/var/snap/graylog/current/default-graylog-server",
636 default_graylog_server,
637 "root",
638 "root",
639 0o644,
640 ),
641 ]
642 )
643 mock_check_call.assert_called_with(["update-ca-certificates"])
diff --git a/src/tests/unit/test_lib.py b/src/tests/unit/test_lib.py
index 2f7a7c3..5849ac5 100644
--- a/src/tests/unit/test_lib.py
+++ b/src/tests/unit/test_lib.py
@@ -12,17 +12,29 @@ from charms.layer.graylog import (
12class TestLibraryUtils(unittest.TestCase):12class TestLibraryUtils(unittest.TestCase):
13 """Test lib.charms.layer.graylog utils."""13 """Test lib.charms.layer.graylog utils."""
1414
15 @mock.patch("charms.layer.graylog.utils.get_flags")
15 @mock.patch("charms.layer.graylog.utils.is_v2")16 @mock.patch("charms.layer.graylog.utils.is_v2")
16 def test_api(self, mock_v2):17 def test_api(self, mock_v2, mock_get_flags):
17 """Validate the api port/url match what we expect for v2 and v3."""18 """Validate the api port/url match what we expect for v2 and v3."""
18 mock_v2.return_value = True19 mock_v2.return_value = True
19 self.assertEqual(get_api_port(), "9001")20 self.assertEqual(get_api_port(), "9001")
21 mock_get_flags.return_value = []
20 self.assertEqual(get_api_url(), "http://127.0.0.1:9001/api/")22 self.assertEqual(get_api_url(), "http://127.0.0.1:9001/api/")
2123
24 mock_get_flags.reset_mock()
25 mock_get_flags.return_value = ["graylog.certificates.configured"]
26 self.assertEqual(get_api_url(), "https://127.0.0.1:9001/api/")
27
22 mock_v2.return_value = False28 mock_v2.return_value = False
23 self.assertEqual(get_api_port(), "9000")29 self.assertEqual(get_api_port(), "9000")
30 mock_get_flags.reset_mock()
31 mock_get_flags.return_value = []
24 self.assertEqual(get_api_url(), "http://127.0.0.1:9000/api/")32 self.assertEqual(get_api_url(), "http://127.0.0.1:9000/api/")
2533
34 mock_get_flags.reset_mock()
35 mock_get_flags.return_value = ["graylog.certificates.configured"]
36 self.assertEqual(get_api_url(), "https://127.0.0.1:9000/api/")
37
26 @mock.patch("charms.layer.graylog.utils.is_v2")38 @mock.patch("charms.layer.graylog.utils.is_v2")
27 def test_validate_api_url(self, mock_v2):39 def test_validate_api_url(self, mock_v2):
28 """Validate the URI suffix is valid for v2 and v3."""40 """Validate the URI suffix is valid for v2 and v3."""

Subscribers

People subscribed via source and target branches

to all changes: