Merge ~peter-sabaini/charm-graylog:lp1758175 into charm-graylog:master
- Git
- lp:~peter-sabaini/charm-graylog
- lp1758175
- Merge into master
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) |
||||
Related bugs: |
|
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.
Commit message
Description of the change
Paul Goins (vultaire) wrote : Posted in a previous version of this proposal | # |
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:/
(It's trying to do an apt-get install ca-certificates
Running functional tests now.
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.
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?
Drew Freiberger (afreiberger) wrote : | # |
Expected status blocked on bionic-
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,
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.
Drew Freiberger (afreiberger) wrote : | # |
Graylog service not starting snap version is 3/stable with tls enabled:
2021-01-
2021-01-
2021-01-
graylog traceback has this as the cause:
Caused by: org.glassfish.
Snap is running graylog with the following cli args: (most specifically: -Djavax.
root 7680 1 0 21:24 ? 00:00:03 /snap/graylog/
Drew Freiberger (afreiberger) wrote : | # |
After adding the sync-graylog-snap file into /etc/ca-
Drew Freiberger (afreiberger) wrote : | # |
Getting random snap core install errors (this was on bionic-graylog3-tls model.
https:/
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.
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.
Preview Diff
1 | diff --git a/src/layer.yaml b/src/layer.yaml |
2 | index 04e6d2a..5173d7e 100644 |
3 | --- a/src/layer.yaml |
4 | +++ b/src/layer.yaml |
5 | @@ -3,6 +3,7 @@ includes: |
6 | - 'layer:basic' |
7 | - 'layer:snap' |
8 | - 'layer:leadership' |
9 | + - 'layer:tls-client' |
10 | - 'interface:elasticsearch' |
11 | - 'interface:elastic-beats' |
12 | - 'interface:http' |
13 | diff --git a/src/lib/charms/layer/graylog/api.py b/src/lib/charms/layer/graylog/api.py |
14 | index 98e52cf..3d9bf93 100644 |
15 | --- a/src/lib/charms/layer/graylog/api.py |
16 | +++ b/src/lib/charms/layer/graylog/api.py |
17 | @@ -4,6 +4,12 @@ import os |
18 | |
19 | import requests |
20 | |
21 | +# When using 'certifi' from the virtualenv, the system-wide certificates store |
22 | +# is not used, so installed certificates won't be used to validate hosts. |
23 | +# Adding the system CA bundle |
24 | +# https://git.launchpad.net/ubuntu/+source/python-certifi/tree/debian/patches/0001-Use-Debian-provided-etc-ssl-certs-ca-certificates.cr.patch |
25 | +SYSTEM_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt" |
26 | + |
27 | # We are in a charm environment |
28 | charm = False |
29 | if os.environ.get("JUJU_UNIT_NAME"): |
30 | @@ -20,7 +26,9 @@ def get_ignore_indexer_failures_file(): # noqa: D103 |
31 | class GraylogApi: |
32 | """Manage Graylog via its API.""" |
33 | |
34 | - def __init__(self, base_url, username, password, token_name="graylog-api"): |
35 | + def __init__( |
36 | + self, base_url, username, password, token_name="graylog-api", verify=None |
37 | + ): |
38 | """Initialize HTTP values.""" |
39 | self.base_url = base_url |
40 | if not base_url.endswith("/"): |
41 | @@ -33,6 +41,7 @@ class GraylogApi: |
42 | self.input_types = None |
43 | self.req_timeout = 3 |
44 | self.req_retries = 4 |
45 | + self.verify = verify |
46 | |
47 | def request(self, path, method="GET", data={}, params=None): # noqa: C901 |
48 | """Retrieve data by URL.""" |
49 | @@ -59,6 +68,7 @@ class GraylogApi: |
50 | params=params, |
51 | headers=headers, |
52 | timeout=self.req_timeout, |
53 | + verify=SYSTEM_CA_BUNDLE if self.verify is None else self.verify, |
54 | ) |
55 | if resp.ok: |
56 | if method == "DELETE": |
57 | diff --git a/src/lib/charms/layer/graylog/constants.py b/src/lib/charms/layer/graylog/constants.py |
58 | index db38aa0..ce2bc59 100644 |
59 | --- a/src/lib/charms/layer/graylog/constants.py |
60 | +++ b/src/lib/charms/layer/graylog/constants.py |
61 | @@ -1,5 +1,9 @@ |
62 | """Options used by the Graylog charm.""" |
63 | +import os |
64 | + |
65 | + |
66 | SNAP_NAME = "graylog" |
67 | +SNAP_COMMON_DIR = "/var/snap/graylog/common/" |
68 | CONF_FILE = "/var/snap/graylog/common/server.conf" |
69 | SNAP_CONF_FILE = "/snap/graylog/current/etc/graylog/server/server.conf" |
70 | # /snap/graylog is a read-only squashfs so we use the initial settings |
71 | @@ -12,3 +16,5 @@ ELASTICSEARCH_DISCOVERY_PORT = "9300" |
72 | SERVICE_NAME = "snap.graylog.graylog" |
73 | DEFAULT_REST_API_TIMEOUT = 120 |
74 | NAGIOS_USERNAME = "nagios" |
75 | +CERT_PATH = os.path.join(SNAP_COMMON_DIR, "server.crt") |
76 | +CERT_KEY_PATH = os.path.join(SNAP_COMMON_DIR, "server.key") |
77 | diff --git a/src/lib/charms/layer/graylog/utils.py b/src/lib/charms/layer/graylog/utils.py |
78 | index 115e672..6b020c5 100644 |
79 | --- a/src/lib/charms/layer/graylog/utils.py |
80 | +++ b/src/lib/charms/layer/graylog/utils.py |
81 | @@ -4,10 +4,10 @@ from urllib.parse import urlparse |
82 | from charmhelpers.core import hookenv |
83 | |
84 | from charms.layer.graylog.constants import SNAP_NAME |
85 | +from charms.reactive import get_flags |
86 | |
87 | import netifaces |
88 | |
89 | - |
90 | # Allow mocking of other layers during unit tests |
91 | try: |
92 | from charms.layer import snap |
93 | @@ -20,7 +20,14 @@ def get_api_port(): # noqa: D103 |
94 | |
95 | |
96 | def get_api_url(): # noqa: D103 |
97 | - return "http://127.0.0.1:{}/api/".format(get_api_port()) |
98 | + return "{}://127.0.0.1:{}/api/".format(get_api_protocol(), get_api_port()) |
99 | + |
100 | + |
101 | +def get_api_protocol(): # noqa: D103 |
102 | + if "graylog.certificates.configured" in get_flags(): |
103 | + return "https" |
104 | + else: |
105 | + return "http" |
106 | |
107 | |
108 | def is_v2(): # noqa: D103 |
109 | diff --git a/src/reactive/graylog.py b/src/reactive/graylog.py |
110 | index 9cc3a93..4f4a9ee 100644 |
111 | --- a/src/reactive/graylog.py |
112 | +++ b/src/reactive/graylog.py |
113 | @@ -2,23 +2,29 @@ |
114 | import hashlib |
115 | import os |
116 | import re |
117 | +import socket |
118 | +import subprocess |
119 | import time |
120 | from urllib.parse import urlparse |
121 | |
122 | from charmhelpers.contrib.charmsupport import nrpe |
123 | -from charmhelpers.core import hookenv, host, unitdata |
124 | +from charmhelpers.core import hookenv, host, templating, unitdata |
125 | +from charmhelpers.fetch import filter_installed_packages, install |
126 | |
127 | import charms.leadership |
128 | -from charms.layer import snap |
129 | +from charms.layer import snap, tls_client |
130 | from charms.layer.graylog import ( |
131 | GraylogApi, |
132 | LogExtractPipeline, |
133 | get_api_port, |
134 | + get_api_protocol, |
135 | get_api_url, |
136 | is_v2, |
137 | validate_api_uri, |
138 | ) |
139 | from charms.layer.graylog.constants import ( |
140 | + CERT_KEY_PATH, |
141 | + CERT_PATH, |
142 | CONF_FILE, |
143 | DEFAULT_REST_API_TIMEOUT, |
144 | ELASTICSEARCH_DISCOVERY_PORT, |
145 | @@ -200,7 +206,8 @@ def configure_graylog(): # noqa: C901 |
146 | # intead of guessing the best IP. |
147 | set_conf("http_bind_address", "0.0.0.0:9000") |
148 | set_conf( |
149 | - "http_publish_uri", validate_api_uri("http://0.0.0.0:{}/".format(api_port)) |
150 | + "http_publish_uri", |
151 | + validate_api_uri("{}://0.0.0.0:{}/".format(get_api_protocol(), api_port)), |
152 | ) |
153 | |
154 | if conf["web_endpoint_uri"]: |
155 | @@ -1045,6 +1052,49 @@ def configure_nagios(nagios): |
156 | set_state("graylog_nagios.configured") |
157 | |
158 | |
159 | +@when("certificates.available") |
160 | +@when_not("graylog.certificates.configured") |
161 | +def tls_request_certificate(tls): |
162 | + """Create a server certificate for this server.""" |
163 | + # ca-certificates-java generates the ca-certificates in a jvm compatible |
164 | + # keystore |
165 | + if filter_installed_packages(["ca-certificates-java"]): |
166 | + install(["ca-certificates-java"], fatal=True) |
167 | + |
168 | + _maybe_install_ca_certificates_hook() |
169 | + _maybe_configure_graylog_jvm_keystore() |
170 | + |
171 | + # Use the public ip of this unit as the Common Name for the certificate. |
172 | + common_name = hookenv.unit_public_ip() |
173 | + # Get a list of Subject Alt Names for the certificate. |
174 | + sans = [] |
175 | + sans.append(hookenv.unit_public_ip()) |
176 | + sans.append(hookenv.unit_private_ip()) |
177 | + sans.append(socket.gethostname()) |
178 | + sans.append("127.0.0.1") |
179 | + sans.append("localhost") |
180 | + tls_client.request_server_cert( |
181 | + common_name, sans, crt_path=CERT_PATH, key_path=CERT_KEY_PATH |
182 | + ) |
183 | + |
184 | + set_conf("http_enable_tls", "true") |
185 | + set_conf("http_tls_cert_file", CERT_PATH) |
186 | + set_conf("http_tls_key_file", CERT_KEY_PATH) |
187 | + |
188 | + set_conf("rest_enable_tls", "true") |
189 | + set_conf("rest_tls_cert_file", CERT_PATH) |
190 | + set_conf("rest_tls_key_file", CERT_KEY_PATH) |
191 | + |
192 | + set_conf("web_enable_tls", "true") |
193 | + set_conf("web_tls_cert_file", CERT_PATH) |
194 | + set_conf("web_tls_key_file", CERT_KEY_PATH) |
195 | + |
196 | + set_conf("http_publish_uri", "https://0.0.0.0:{}/".format(get_api_port())) |
197 | + |
198 | + set_state("graylog.certificates.configured") |
199 | + set_state("graylog.needs_restart") |
200 | + |
201 | + |
202 | def get_default_graylog_client(): # noqa: D103 |
203 | db = unitdata.kv() |
204 | return GraylogApi( |
205 | @@ -1068,3 +1118,22 @@ def _verify_rest_api_is_alive(timeout=DEFAULT_REST_API_TIMEOUT): |
206 | if time.time() - start_ts > timeout: |
207 | raise ApiTimeout() |
208 | hookenv.log("REST API is up") |
209 | + |
210 | + |
211 | +def _maybe_install_ca_certificates_hook(): |
212 | + templating.render( |
213 | + "sync-graylog-snap", |
214 | + "/etc/ca-certificates/update.d/sync-graylog-snap", |
215 | + context={}, |
216 | + perms=0o755, |
217 | + ) |
218 | + subprocess.check_call(["update-ca-certificates"]) |
219 | + |
220 | + |
221 | +def _maybe_configure_graylog_jvm_keystore(): |
222 | + templating.render( |
223 | + "default-graylog-server", |
224 | + "/var/snap/graylog/current/default-graylog-server", |
225 | + context={}, |
226 | + perms=0o644, |
227 | + ) |
228 | diff --git a/src/templates/default-graylog-server b/src/templates/default-graylog-server |
229 | new file mode 100644 |
230 | index 0000000..ace24cb |
231 | --- /dev/null |
232 | +++ b/src/templates/default-graylog-server |
233 | @@ -0,0 +1,4 @@ |
234 | +# |
235 | +# This file is managed by juju. |
236 | +# |
237 | +GRAYLOG_SERVER_JAVA_OPTS="-Djavax.net.ssl.trustStore=/var/snap/graylog/common/cacerts" |
238 | diff --git a/src/templates/sync-graylog-snap b/src/templates/sync-graylog-snap |
239 | new file mode 100755 |
240 | index 0000000..6d08b7d |
241 | --- /dev/null |
242 | +++ b/src/templates/sync-graylog-snap |
243 | @@ -0,0 +1,7 @@ |
244 | +#!/bin/sh |
245 | +# |
246 | +# This file is managed by juju. |
247 | +# |
248 | + |
249 | +set -e |
250 | +rsync /etc/ssl/certs/java/cacerts /var/snap/graylog/common/cacerts |
251 | diff --git a/src/tests/functional/requirements.txt b/src/tests/functional/requirements.txt |
252 | index 7345526..ecf87be 100644 |
253 | --- a/src/tests/functional/requirements.txt |
254 | +++ b/src/tests/functional/requirements.txt |
255 | @@ -1,2 +1,4 @@ |
256 | tenacity |
257 | git+https://github.com/openstack-charmers/zaza.git#egg=zaza |
258 | +netifaces |
259 | +charmhelpers |
260 | diff --git a/src/tests/functional/tests/base.py b/src/tests/functional/tests/base.py |
261 | new file mode 100644 |
262 | index 0000000..46eb75f |
263 | --- /dev/null |
264 | +++ b/src/tests/functional/tests/base.py |
265 | @@ -0,0 +1,48 @@ |
266 | +"""Base class for Testing.""" |
267 | +import logging |
268 | +import os |
269 | +import tempfile |
270 | +import unittest |
271 | + |
272 | +from zaza import model |
273 | + |
274 | + |
275 | +class BaseTestCase(unittest.TestCase): |
276 | + """Base class for graylog functional testing.""" |
277 | + |
278 | + @classmethod |
279 | + def setUpClass(cls): |
280 | + """Configure the test's environment. |
281 | + |
282 | + - Get the CA from easyrsa (when available) and write it on disk. |
283 | + """ |
284 | + if cls.get_api_protocol() == "https": |
285 | + rel_id = model.get_relation_id( |
286 | + "graylog", "easyrsa", remote_interface_name="client" |
287 | + ) |
288 | + easyrsa_unit = model.get_units("easyrsa")[0] |
289 | + cmd = "relation-get -r client:{} ca {}".format( |
290 | + rel_id, easyrsa_unit.entity_id |
291 | + ) |
292 | + logging.info(cmd) |
293 | + result = model.run_on_unit(easyrsa_unit.entity_id, cmd) |
294 | + |
295 | + with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: |
296 | + f.write(result["Stdout"]) |
297 | + f.flush() |
298 | + cls.ca_path = f.name |
299 | + else: |
300 | + cls.ca_path = None |
301 | + |
302 | + @classmethod |
303 | + def tearDownClass(cls): |
304 | + """Remove the CA file that was written to disk during the setUp.""" |
305 | + if cls.ca_path: |
306 | + os.remove(cls.ca_path) |
307 | + |
308 | + @classmethod |
309 | + def get_api_protocol(cls): # noqa: D102 |
310 | + if model.get_relation_id("graylog", "easyrsa"): |
311 | + return "https" |
312 | + else: |
313 | + return "http" |
314 | diff --git a/src/tests/functional/tests/bundles/bionic-graylog2.yaml b/src/tests/functional/tests/bundles/bionic-graylog2.yaml |
315 | index bd256db..27703fd 100644 |
316 | --- a/src/tests/functional/tests/bundles/bionic-graylog2.yaml |
317 | +++ b/src/tests/functional/tests/bundles/bionic-graylog2.yaml |
318 | @@ -30,6 +30,10 @@ applications: |
319 | nrpe: |
320 | charm: cs:nrpe |
321 | |
322 | + nagios: |
323 | + charm: cs:nagios |
324 | + num_units: 1 |
325 | + |
326 | relations: |
327 | - - ubuntu |
328 | - filebeat |
329 | @@ -43,3 +47,5 @@ relations: |
330 | - haproxy |
331 | - - graylog |
332 | - nrpe |
333 | + - - nagios |
334 | + - nrpe |
335 | diff --git a/src/tests/functional/tests/bundles/bionic-graylog3-tls.yaml b/src/tests/functional/tests/bundles/bionic-graylog3-tls.yaml |
336 | new file mode 100644 |
337 | index 0000000..954f9c5 |
338 | --- /dev/null |
339 | +++ b/src/tests/functional/tests/bundles/bionic-graylog3-tls.yaml |
340 | @@ -0,0 +1,51 @@ |
341 | +series: bionic |
342 | + |
343 | +applications: |
344 | + ubuntu: |
345 | + charm: cs:ubuntu |
346 | + num_units: 1 |
347 | + |
348 | + filebeat: |
349 | + charm: cs:filebeat |
350 | + num_units: 0 |
351 | + |
352 | + graylog: |
353 | + num_units: 1 |
354 | + series: bionic |
355 | + options: |
356 | + channel: 3/stable |
357 | + |
358 | + elastic: |
359 | + charm: cs:elasticsearch |
360 | + num_units: 1 |
361 | + |
362 | + mongo: |
363 | + charm: cs:mongodb |
364 | + num_units: 1 |
365 | + |
366 | + nrpe: |
367 | + charm: cs:nrpe |
368 | + |
369 | + nagios: |
370 | + charm: cs:nagios |
371 | + num_units: 1 |
372 | + |
373 | + easyrsa: |
374 | + charm: cs:~containers/easyrsa |
375 | + num_units: 1 |
376 | + |
377 | +relations: |
378 | + - - ubuntu |
379 | + - filebeat |
380 | + - - graylog:beats |
381 | + - filebeat:logstash |
382 | + - - graylog |
383 | + - mongo |
384 | + - - graylog |
385 | + - elastic |
386 | + - - graylog |
387 | + - nrpe |
388 | + - - nagios |
389 | + - nrpe |
390 | + - - easyrsa:client |
391 | + - graylog:certificates |
392 | diff --git a/src/tests/functional/tests/bundles/bionic-graylog3.yaml b/src/tests/functional/tests/bundles/bionic-graylog3.yaml |
393 | index 596cf7d..33ac7e5 100644 |
394 | --- a/src/tests/functional/tests/bundles/bionic-graylog3.yaml |
395 | +++ b/src/tests/functional/tests/bundles/bionic-graylog3.yaml |
396 | @@ -30,6 +30,10 @@ applications: |
397 | nrpe: |
398 | charm: cs:nrpe |
399 | |
400 | + nagios: |
401 | + charm: cs:nagios |
402 | + num_units: 1 |
403 | + |
404 | relations: |
405 | - - ubuntu |
406 | - filebeat |
407 | @@ -43,3 +47,5 @@ relations: |
408 | - haproxy |
409 | - - graylog |
410 | - nrpe |
411 | + - - nagios |
412 | + - nrpe |
413 | diff --git a/src/tests/functional/tests/bundles/focal-graylog2.yaml b/src/tests/functional/tests/bundles/focal-graylog2.yaml |
414 | index 368432e..9cc6016 100644 |
415 | --- a/src/tests/functional/tests/bundles/focal-graylog2.yaml |
416 | +++ b/src/tests/functional/tests/bundles/focal-graylog2.yaml |
417 | @@ -30,6 +30,10 @@ applications: |
418 | nrpe: |
419 | charm: cs:nrpe |
420 | |
421 | + nagios: |
422 | + charm: cs:nagios |
423 | + num_units: 1 |
424 | + |
425 | relations: |
426 | - - ubuntu |
427 | - filebeat |
428 | @@ -43,3 +47,5 @@ relations: |
429 | - haproxy |
430 | - - graylog |
431 | - nrpe |
432 | + - - nagios |
433 | + - nrpe |
434 | diff --git a/src/tests/functional/tests/bundles/focal-graylog3-tls.yaml b/src/tests/functional/tests/bundles/focal-graylog3-tls.yaml |
435 | new file mode 100644 |
436 | index 0000000..100ccb4 |
437 | --- /dev/null |
438 | +++ b/src/tests/functional/tests/bundles/focal-graylog3-tls.yaml |
439 | @@ -0,0 +1,51 @@ |
440 | +series: bionic |
441 | + |
442 | +applications: |
443 | + ubuntu: |
444 | + charm: cs:ubuntu |
445 | + num_units: 1 |
446 | + |
447 | + filebeat: |
448 | + charm: cs:filebeat |
449 | + num_units: 0 |
450 | + |
451 | + graylog: |
452 | + num_units: 1 |
453 | + series: focal |
454 | + options: |
455 | + channel: 3/stable |
456 | + |
457 | + elastic: |
458 | + charm: cs:elasticsearch |
459 | + num_units: 1 |
460 | + |
461 | + mongo: |
462 | + charm: cs:mongodb |
463 | + num_units: 1 |
464 | + |
465 | + nrpe: |
466 | + charm: cs:nrpe |
467 | + |
468 | + nagios: |
469 | + charm: cs:nagios |
470 | + num_units: 1 |
471 | + |
472 | + easyrsa: |
473 | + charm: cs:~containers/easyrsa |
474 | + num_units: 1 |
475 | + |
476 | +relations: |
477 | + - - ubuntu |
478 | + - filebeat |
479 | + - - graylog:beats |
480 | + - filebeat:logstash |
481 | + - - graylog |
482 | + - mongo |
483 | + - - graylog |
484 | + - elastic |
485 | + - - graylog |
486 | + - nrpe |
487 | + - - nagios |
488 | + - nrpe |
489 | + - - easyrsa:client |
490 | + - graylog:certificates |
491 | diff --git a/src/tests/functional/tests/bundles/focal-graylog3.yaml b/src/tests/functional/tests/bundles/focal-graylog3.yaml |
492 | index cb26f52..e2e2a41 100644 |
493 | --- a/src/tests/functional/tests/bundles/focal-graylog3.yaml |
494 | +++ b/src/tests/functional/tests/bundles/focal-graylog3.yaml |
495 | @@ -30,6 +30,10 @@ applications: |
496 | nrpe: |
497 | charm: cs:nrpe |
498 | |
499 | + nagios: |
500 | + charm: cs:nagios |
501 | + num_units: 1 |
502 | + |
503 | relations: |
504 | - - ubuntu |
505 | - filebeat |
506 | @@ -43,3 +47,5 @@ relations: |
507 | - haproxy |
508 | - - graylog |
509 | - nrpe |
510 | + - - nagios |
511 | + - nrpe |
512 | diff --git a/src/tests/functional/tests/test_graylog_charm.py b/src/tests/functional/tests/test_graylog_charm.py |
513 | index df763b6..83d58e7 100644 |
514 | --- a/src/tests/functional/tests/test_graylog_charm.py |
515 | +++ b/src/tests/functional/tests/test_graylog_charm.py |
516 | @@ -2,12 +2,13 @@ |
517 | |
518 | import logging |
519 | import time |
520 | -import unittest |
521 | |
522 | from api import GraylogApi |
523 | |
524 | import tenacity |
525 | |
526 | +from tests.base import BaseTestCase |
527 | + |
528 | import zaza.model as model |
529 | |
530 | |
531 | @@ -17,7 +18,7 @@ DEFAULT_API_PORT = "9001" |
532 | DEFAULT_WEB_PORT = "9000" |
533 | |
534 | |
535 | -class BaseGraylogTest(unittest.TestCase): |
536 | +class BaseGraylogTest(BaseTestCase): |
537 | """Base for Graylog charm tests.""" |
538 | |
539 | @classmethod |
540 | @@ -56,10 +57,14 @@ class BaseGraylogTest(unittest.TestCase): |
541 | "org.graylog.plugins.threatintel.ThreatIntelPlugin", |
542 | } |
543 | |
544 | - api_url = "http://{}:{}/api".format(cls.graylog_ip, cls.api_port) |
545 | + api_url = "{}://{}:{}/api".format( |
546 | + cls.get_api_protocol(), cls.graylog_ip, cls.api_port |
547 | + ) |
548 | action = model.run_action_on_leader(cls.application_name, "show-admin-password") |
549 | res = action.data["results"] |
550 | - cls.api = GraylogApi(api_url, "admin", res["admin-password"]) |
551 | + cls.api = GraylogApi( |
552 | + api_url, "admin", res["admin-password"], verify=cls.ca_path |
553 | + ) |
554 | # try harder to get an api connection to cater for overloaded testsystems |
555 | cls.api.req_timeout = REQ_TIMEOUT |
556 | logging.debug("API at {}".format(api_url)) |
557 | @@ -75,7 +80,9 @@ class CharmOperationTest(BaseGraylogTest): |
558 | until the CURL_TIMEOUT because it may take a few seconds for the |
559 | graylog systemd service to start. |
560 | """ |
561 | - curl_command = "curl http://localhost:{}".format(self.api_port) |
562 | + curl_command = "curl {}://localhost:{}".format( |
563 | + self.get_api_protocol(), self.api_port |
564 | + ) |
565 | timeout = time.time() + CURL_TIMEOUT |
566 | while time.time() < timeout: |
567 | response = model.run_on_unit(self.lead_unit_name, curl_command) |
568 | diff --git a/src/tests/functional/tests/test_legacy.py b/src/tests/functional/tests/test_legacy.py |
569 | index 5ee2eea..fcea119 100644 |
570 | --- a/src/tests/functional/tests/test_legacy.py |
571 | +++ b/src/tests/functional/tests/test_legacy.py |
572 | @@ -1,9 +1,14 @@ |
573 | """Graylog v2 legacy tests.""" |
574 | +import logging |
575 | import re |
576 | import unittest |
577 | |
578 | from api import GraylogApi |
579 | |
580 | +from charmhelpers.core.decorators import retry_on_exception |
581 | + |
582 | +from tests.base import BaseTestCase |
583 | + |
584 | import yaml |
585 | |
586 | from zaza import model |
587 | @@ -14,7 +19,7 @@ DEFAULT_API_PORT = "9001" # Graylog 2 only |
588 | DEFAULT_WEB_PORT = "9000" |
589 | |
590 | |
591 | -class LegacyTests(unittest.TestCase): |
592 | +class LegacyTests(BaseTestCase): |
593 | """These tests were ported from tests/test_10_basic.py in changeset a3b54c2. |
594 | |
595 | They were temporarily deleted during the conversion from Amulet to Zaza. |
596 | @@ -24,8 +29,12 @@ class LegacyTests(unittest.TestCase): |
597 | |
598 | def test_api_ready(self): |
599 | """Curl the api endpoint on the graylog unit.""" |
600 | + protocol = self.get_api_protocol() |
601 | port = self.get_api_port() |
602 | - curl_command = "curl http://localhost:{} --connect-timeout 30".format(port) |
603 | + curl_command = ("curl {}://localhost:{} " "--connect-timeout 30").format( |
604 | + protocol, port |
605 | + ) |
606 | + logging.info(curl_command) |
607 | self.run_command("graylog/0", curl_command) |
608 | |
609 | def test_elasticsearch_active(self): |
610 | @@ -60,10 +69,16 @@ class LegacyTests(unittest.TestCase): |
611 | """Return the list of API clients to Graylog units.""" |
612 | graylog_units = juju.get_full_juju_status().applications["graylog"]["units"] |
613 | graylog_addrs = [unit["public-address"] for unit in graylog_units.values()] |
614 | + protocol = self.get_api_protocol() |
615 | port = self.get_api_port() |
616 | password = self.get_graylog_admin_password() |
617 | return [ |
618 | - GraylogApi("http://{}:{}/api".format(graylog_addr, port), "admin", password) |
619 | + GraylogApi( |
620 | + "{}://{}:{}/api".format(protocol, graylog_addr, port), |
621 | + "admin", |
622 | + password, |
623 | + verify=self.ca_path, |
624 | + ) |
625 | for graylog_addr in graylog_addrs |
626 | ] |
627 | |
628 | @@ -76,8 +91,17 @@ class LegacyTests(unittest.TestCase): |
629 | password = result.data["results"]["admin-password"] |
630 | return password |
631 | |
632 | - def test_website_active(self): |
633 | + def test_website_active_with_haproxy(self): |
634 | """Verify if the Graylog endpoints are correctly configured.""" |
635 | + try: |
636 | + model.get_application("haproxy") |
637 | + except KeyError: |
638 | + reason = ( |
639 | + "haproxy is not in the model, assuming there is " |
640 | + "no front loadbalancer" |
641 | + ) |
642 | + raise unittest.SkipTest(reason) |
643 | + |
644 | data = juju.get_relation_from_unit("haproxy/0", "graylog/0", "website") |
645 | self.assertEqual(data["port"], DEFAULT_WEB_PORT) |
646 | |
647 | @@ -117,6 +141,7 @@ class LegacyTests(unittest.TestCase): |
648 | ) |
649 | self.assertTrue(re.search(r"CRITICAL", cfg)) |
650 | |
651 | + @retry_on_exception(num_retries=5, base_delay=2, exc_type=AssertionError) |
652 | def get_file_contents(self, unit, filename): |
653 | """Retrieve content of a file in a remote unit.""" |
654 | result = self.run_command(unit, "cat '{}'".format(filename)) |
655 | diff --git a/src/tests/functional/tests/tests.yaml b/src/tests/functional/tests/tests.yaml |
656 | index c52c200..47db931 100644 |
657 | --- a/src/tests/functional/tests/tests.yaml |
658 | +++ b/src/tests/functional/tests/tests.yaml |
659 | @@ -1,9 +1,11 @@ |
660 | charm_name: graylog |
661 | gate_bundles: |
662 | - gl2: bionic-graylog2 |
663 | - - gl3: bionic-graylog3 |
664 | - gl2: focal-graylog2 |
665 | + - gl3: bionic-graylog3 |
666 | + - gl3: bionic-graylog3-tls |
667 | - gl3: focal-graylog3 |
668 | + - gl3: focal-graylog3-tls |
669 | # - gl2: bionic-graylog2-ha |
670 | # - gl3: bionic-graylog3-ha |
671 | smoke_bundles: |
672 | @@ -28,3 +30,15 @@ target_deploy_status: |
673 | nrpe: |
674 | workload-status: blocked |
675 | workload-status-message: Nagios server not configured or related |
676 | + mongo: |
677 | + workload-status: active |
678 | + workload-status-message: 'Unit is ready' |
679 | + graylog: |
680 | + workload-status: active |
681 | + workload-status-message: 'Ready with: filebeat, elasticsearch, mongodb' |
682 | + elastic: |
683 | + workload-status: active |
684 | + workload-status-message: 'Unit is ready' |
685 | + easyrsa: |
686 | + workload-status-message: 'Certificate Authority connected.' |
687 | + |
688 | diff --git a/src/tests/unit/test_graylog.py b/src/tests/unit/test_graylog.py |
689 | index c723ce1..a9b0ae8 100644 |
690 | --- a/src/tests/unit/test_graylog.py |
691 | +++ b/src/tests/unit/test_graylog.py |
692 | @@ -1,10 +1,15 @@ |
693 | """Tests around the graylog reactive script.""" |
694 | import os |
695 | +import socket |
696 | import sys |
697 | import tempfile |
698 | import unittest |
699 | from unittest import mock |
700 | |
701 | +from charms.layer.graylog.constants import ( |
702 | + CERT_KEY_PATH, |
703 | + CERT_PATH, |
704 | +) |
705 | from charms.layer.graylog.snap_change import ( |
706 | ChannelChangeStatus, |
707 | _is_channel_valid, |
708 | @@ -17,6 +22,8 @@ leader_mock = mock.Mock() |
709 | sys.modules["charms.leadership"] = leader_mock |
710 | snap_mock = mock.Mock() |
711 | sys.modules["charms.layer.snap"] = snap_mock |
712 | +tls_client_mock = mock.Mock() |
713 | +sys.modules["charms.layer.tls_client"] = tls_client_mock |
714 | |
715 | from files import check_graylog_health # noqa: E402 |
716 | |
717 | @@ -28,6 +35,7 @@ from reactive.graylog import ( # noqa: E402 |
718 | refresh_graylog, |
719 | set_conf, |
720 | set_jvm_heap_size, |
721 | + tls_request_certificate, |
722 | update_config, |
723 | upgrade_charm, |
724 | ) |
725 | @@ -555,3 +563,81 @@ class TestIsChannelValid(unittest.TestCase): |
726 | "2", # track w/o risk |
727 | ): |
728 | self.assertFalse(_is_channel_valid(channel)) |
729 | + |
730 | + |
731 | +class TestTlsClient(unittest.TestCase): |
732 | + """Test TLS Client integration.""" |
733 | + |
734 | + @mock.patch("charmhelpers.core.host.mkdir") |
735 | + @mock.patch("charmhelpers.core.host.write_file") |
736 | + @mock.patch("charmhelpers.core.hookenv.charm_dir") |
737 | + @mock.patch("charms.layer.graylog.utils.is_v2") |
738 | + @mock.patch("reactive.graylog.set_conf") |
739 | + @mock.patch("reactive.graylog.hookenv.unit_public_ip") |
740 | + @mock.patch("reactive.graylog.hookenv.unit_private_ip") |
741 | + @mock.patch("reactive.graylog.install") |
742 | + @mock.patch("subprocess.check_call") |
743 | + def test_request_certificate( |
744 | + self, |
745 | + mock_check_call, |
746 | + mock_install, |
747 | + mock_private_ip, |
748 | + mock_public_ip, |
749 | + mock_set_conf, |
750 | + mock_is_v2, |
751 | + mock_charm_dir, |
752 | + mock_write_file, |
753 | + mock_mkdir, |
754 | + ): # noqa: D102 |
755 | + charm_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../") |
756 | + mock_charm_dir.return_value = charm_dir |
757 | + mock_is_v2.return_value = False |
758 | + mock_public_ip.return_value = "1.2.3.4" |
759 | + mock_private_ip.return_value = "5.6.7.8" |
760 | + mock_tls = mock.Mock() |
761 | + tls_request_certificate(mock_tls) |
762 | + |
763 | + tls_client_mock.request_server_cert.assert_called_with( |
764 | + "1.2.3.4", |
765 | + ["1.2.3.4", "5.6.7.8", socket.gethostname(), "127.0.0.1", "localhost"], |
766 | + crt_path=CERT_PATH, |
767 | + key_path=CERT_KEY_PATH, |
768 | + ) |
769 | + mock_set_conf.assert_has_calls( |
770 | + [ |
771 | + mock.call("rest_enable_tls", "true"), |
772 | + mock.call("rest_tls_cert_file", CERT_PATH), |
773 | + mock.call("rest_tls_key_file", CERT_KEY_PATH), |
774 | + mock.call("web_enable_tls", "true"), |
775 | + mock.call("web_tls_cert_file", CERT_PATH), |
776 | + mock.call("web_tls_key_file", CERT_KEY_PATH), |
777 | + ] |
778 | + ) |
779 | + |
780 | + with open(os.path.join(charm_dir, "templates/sync-graylog-snap"), "rb") as f: |
781 | + sync_script = f.read().strip() |
782 | + |
783 | + with open( |
784 | + os.path.join(charm_dir, "templates/default-graylog-server"), "rb" |
785 | + ) as f: |
786 | + default_graylog_server = f.read().strip() |
787 | + |
788 | + mock_write_file.assert_has_calls( |
789 | + [ |
790 | + mock.call( |
791 | + "/etc/ca-certificates/update.d/sync-graylog-snap", |
792 | + sync_script, |
793 | + "root", |
794 | + "root", |
795 | + 0o755, |
796 | + ), |
797 | + mock.call( |
798 | + "/var/snap/graylog/current/default-graylog-server", |
799 | + default_graylog_server, |
800 | + "root", |
801 | + "root", |
802 | + 0o644, |
803 | + ), |
804 | + ] |
805 | + ) |
806 | + mock_check_call.assert_called_with(["update-ca-certificates"]) |
807 | diff --git a/src/tests/unit/test_lib.py b/src/tests/unit/test_lib.py |
808 | index 2f7a7c3..5849ac5 100644 |
809 | --- a/src/tests/unit/test_lib.py |
810 | +++ b/src/tests/unit/test_lib.py |
811 | @@ -12,17 +12,29 @@ from charms.layer.graylog import ( |
812 | class TestLibraryUtils(unittest.TestCase): |
813 | """Test lib.charms.layer.graylog utils.""" |
814 | |
815 | + @mock.patch("charms.layer.graylog.utils.get_flags") |
816 | @mock.patch("charms.layer.graylog.utils.is_v2") |
817 | - def test_api(self, mock_v2): |
818 | + def test_api(self, mock_v2, mock_get_flags): |
819 | """Validate the api port/url match what we expect for v2 and v3.""" |
820 | mock_v2.return_value = True |
821 | self.assertEqual(get_api_port(), "9001") |
822 | + mock_get_flags.return_value = [] |
823 | self.assertEqual(get_api_url(), "http://127.0.0.1:9001/api/") |
824 | |
825 | + mock_get_flags.reset_mock() |
826 | + mock_get_flags.return_value = ["graylog.certificates.configured"] |
827 | + self.assertEqual(get_api_url(), "https://127.0.0.1:9001/api/") |
828 | + |
829 | mock_v2.return_value = False |
830 | self.assertEqual(get_api_port(), "9000") |
831 | + mock_get_flags.reset_mock() |
832 | + mock_get_flags.return_value = [] |
833 | self.assertEqual(get_api_url(), "http://127.0.0.1:9000/api/") |
834 | |
835 | + mock_get_flags.reset_mock() |
836 | + mock_get_flags.return_value = ["graylog.certificates.configured"] |
837 | + self.assertEqual(get_api_url(), "https://127.0.0.1:9000/api/") |
838 | + |
839 | @mock.patch("charms.layer.graylog.utils.is_v2") |
840 | def test_validate_api_url(self, mock_v2): |
841 | """Validate the URI suffix is valid for v2 and v3.""" |
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.