Merge ~vultaire/charm-graylog:upgrade-tests-and-reworks-20200724 into charm-graylog:master

Proposed by Paul Goins
Status: Rejected
Rejected by: Paul Goins
Proposed branch: ~vultaire/charm-graylog:upgrade-tests-and-reworks-20200724
Merge into: charm-graylog:master
Prerequisite: ~vultaire/charm-graylog:es-and-mongo-version-checks-disabled-v2
Diff against target: 726 lines (+607/-10) (has conflicts)
13 files modified
lib/charms/layer/graylog/api.py (+231/-0)
lib/charms/layer/graylog/utils.py (+0/-1)
lib/charms/layer/graylog/version_check.py (+2/-8)
src/tests/functional/tests/__init__.py (+1/-1)
src/tests/functional/tests/bundles/bionic-graylog2.yaml (+17/-0)
src/tests/functional/tests/bundles/bionic-graylog3.yaml (+17/-0)
src/tests/unit/requirements.txt (+1/-0)
tests/requirements.txt (+7/-0)
tests/shared.py (+40/-0)
tests/test_graylog_charm.py (+117/-0)
tests/test_legacy.py (+83/-0)
tests/test_upgrade.py (+65/-0)
tests/tests.yaml (+26/-0)
Conflict in lib/charms/layer/graylog/api.py
Conflict in lib/charms/layer/graylog/logextract.py
Conflict in lib/charms/layer/graylog/utils.py
Conflict in reactive/graylog.py
Conflict in src/lib/charms/layer/graylog/api.py
Conflict in src/lib/charms/layer/graylog/logextract.py
Conflict in src/lib/charms/layer/graylog/utils.py
Conflict in src/tests/functional/tests/test_graylog_charm.py
Conflict in src/tests/functional/tests/test_legacy.py
Conflict in src/tests/functional/tests/tests.yaml
Conflict in tests/requirements.txt
Conflict in tests/test_graylog_charm.py
Conflict in tests/test_legacy.py
Conflict in tests/tests.yaml
Conflict in unit_tests/test_lib.py
Reviewer Review Type Date Requested Status
Paul Goins Disapprove
Drew Freiberger (community) Needs Fixing
Review via email: mp+388097@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Paul Goins (vultaire) wrote :

Reworked my pending test code, pulling all of the non-pipeline-related tests together, and rebasing onto a more recent branch.

I have not run tests yet on this, hence still marked as WIP.

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

Unit and functional tests should all now be passing. I'm doing one more clean run of functional tests, after which I'll mark this as ready for review.

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

Tests are confirmed passing. Ready for review.

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

Much of this code appears to have landed since this MR. can you please review for bits added in this that may be missing from master? pathing has changed during 20.08 release cycle and will require major rebase and rework.

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

Abandoning in favor of pipelines-2021.

review: Disapprove

Unmerged commits

5714aec... by Paul Goins

Oops; rename problem. Fixed.

9663997... by Paul Goins

Fixed unit test breakage

Yes, I could have got fancy and tried to avoid the netifaces dependency,
but there seemed little point; the library is small and I'd rather keep
the code looking clean without doing tricks such as performing imports
in the functions which use the libraries.

3fb2191... by Paul Goins

Minor changes to test_upgrade.py

79f3a4a... by Paul Goins

Added another prereq

2b1aebd... by Paul Goins

Removed some test helper bits in favor of installing libs for testing

fc1d113... by Paul Goins

Adding testing prereqs

2795245... by Paul Goins

Fixed mistake in reworked is_v2() helper

ea64132... by Paul Goins

Updates to shared test code:

* is_v2() should be based upon the config of the graylog unit. This
  is just in case a bug installs the wrong version of Graylog - if we
  test based upon the installed version, we'll miss bugs, whereas if
  we test based upon the config, that is less likely.

* I also tried to clean things up to remove the hardcoded 'graylog'
  and 'graylog/0' dependencies.

504b1f4... by Paul Goins

Fixed test_graylog_charm.py, updated shared code

* Set @staticmethod/@classmethod decorators on base class methods
  to enable use during setUpClass().

* Updated test_graylog_charm.py to leverage the shared helpers to
  simplify setup and replace API constant use with get_api_port().

336e271... by Paul Goins

Extracted new shared test logic module

* Extracted BaseTest superclass for sharing several helper functions.

* DEFAULT_API_PORT and DEFAULT_WEB_PORT also moved here.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/charms/layer/graylog/api.py b/lib/charms/layer/graylog/api.py
2new file mode 100644
3index 0000000..825a70e
4--- /dev/null
5+++ b/lib/charms/layer/graylog/api.py
6@@ -0,0 +1,231 @@
7+<<<<<<< lib/charms/layer/graylog/api.py
8+=======
9+#
10+# " "
11+# mmm m m mmm m m
12+# # # # # # #
13+# # # # # # #
14+# # "mm"# # "mm"#
15+# # #
16+# "" ""
17+# This file is managed by Juju. Local changes will be lost.
18+import json
19+import os
20+import requests
21+
22+from charmhelpers.core import hookenv
23+from charmhelpers.core.hookenv import log
24+
25+
26+def get_ignore_indexer_failures_file():
27+ return "/usr/local/lib/nagios/plugins/ignore_indexer_failures.timestamp"
28+
29+
30+class GraylogApi:
31+
32+ def __init__(self, base_url, username, password, token_name='graylog-api'):
33+ self.base_url = base_url
34+ if not base_url.endswith('/'):
35+ self.base_url += '/'
36+ self.username = username
37+ self.password = password
38+ self.auth = (self.username, self.password)
39+ self.token_name = token_name
40+ self.token = None
41+ self.input_types = None
42+ self.req_timeout = 3
43+ self.req_retries = 4
44+
45+ def request(self, path, method='GET', data={}, params=None):
46+ if path.startswith('/'):
47+ path = path[1:]
48+ url = '{}{}?pretty=true'.format(self.base_url, path)
49+ headers = {
50+ 'Accept': 'application/json',
51+ 'Content-Type': 'application/json',
52+ 'Accept-Encoding': '',
53+ 'X-Requested-By': 'Graylog Charm'
54+ }
55+ if data:
56+ data = json.dumps(data, indent=True)
57+ tries = 0
58+ while tries < self.req_retries:
59+ tries += 1
60+ try:
61+ resp = requests.api.request(method, url, auth=self.auth,
62+ data=data, params=params,
63+ headers=headers, timeout=self.req_timeout)
64+ if resp.ok:
65+ if method == 'DELETE':
66+ return True
67+ return json.loads(resp.content.decode('utf-8'))
68+ else:
69+ msg = 'API error code: {}'.format(resp.status_code)
70+ log(msg)
71+ hookenv.status_set('blocked', msg)
72+ except Exception as ex:
73+ msg = 'Error calling graylog api: {}'.format(ex)
74+ log(msg)
75+
76+ def token_get(self, token_name=None, halt=False):
77+ if self.token:
78+ return self.token
79+ if not token_name:
80+ token_name = self.token_name
81+ if self.password == 'token':
82+ self.token = self.username
83+ self.auth = (self.token, 'token')
84+ return self.token
85+ url = 'users/{}/tokens'.format(self.username)
86+ resp = self.request(url)
87+ if not resp:
88+ return
89+ for token in resp['tokens']:
90+ if token['name'] == token_name:
91+ self.token = token['token']
92+ self.auth = (token['token'], 'token')
93+ return self.token
94+
95+ # None found, let's create a new
96+ url = '{}/{}'.format(url, token_name)
97+ resp = self.request(url, method='POST')
98+ if not halt:
99+ return self.token_get(token_name=token_name, halt=True)
100+ else:
101+ hookenv.status_set('blocked', 'Cannot get API token.')
102+ return None # Don't loop until we run out of memory.
103+
104+ def index_set_get(self, index_id=None):
105+ if not self.token:
106+ self.token_get()
107+ url = 'system/indices/index_sets'
108+ if index_id:
109+ return self.request('{}/{}'.format(url, index_id))
110+ resp = self.request(url)
111+ if resp:
112+ return resp.get('index_sets', None)
113+
114+ def index_set_update(self, index_id, data):
115+ if not self.token:
116+ self.token_get()
117+ url = 'system/indices/index_sets/{}'.format(index_id)
118+ return self.request(url, method='PUT', data=data)
119+
120+ def log_input_get(self, input_id=None):
121+ if not self.token:
122+ self.token_get()
123+ url = 'system/inputs'
124+ if input_id:
125+ return self.request('{}/{}'.format(url, input_id))
126+ resp = self.request(url)
127+ if resp:
128+ return resp.get('inputs', None)
129+
130+ def log_input_update(self, input_id=None, data=None):
131+ if not self.token:
132+ self.token_get()
133+ url = 'system/inputs/'
134+ if input_id:
135+ method = 'PUT'
136+ url += input_id
137+ else:
138+ method = 'POST'
139+ return self.request(url, method=method, data=data)
140+
141+ def log_input_remove(self, input_id):
142+ if not self.token:
143+ self.token_get()
144+ url = 'system/inputs/{}'.format(input_id)
145+ return self.request(url, method='DELETE')
146+
147+ def map_log_input_type(self, type):
148+ """
149+ Map specified int types to Gray's input type objects. Examples:
150+
151+ "Beats" -> org.graylog.plugins.beats.BeatsInput
152+ "Raw TCP" -> org.graylog2.inputs.raw.tcp.RawTCPInput
153+ "Syslog UDP" -> org.graylog2.inputs.syslog.udp.SyslogUDPInput
154+ """
155+ if not self.input_types:
156+ url = 'system/inputs/types'
157+ result = self.request(url)
158+ if result is not None:
159+ self.input_types = result['types']
160+ else:
161+ return None
162+ for k, v in self.input_types.items():
163+ if v.lower().find(type.lower()) != -1:
164+ return k
165+ # For raw input types, map something like "Raw TCP" and "Raw UDP"
166+ # instead of requiring the exact name, "Raw/Plaintext TCP".
167+ if k.lower().find('.'.join(type.split()).lower()) != -1:
168+ return k
169+ return None
170+
171+ def cluster_get(self):
172+ if not self.token:
173+ self.token_get()
174+ url = 'cluster'
175+ return self.request(url)
176+
177+ def user_get(self, username):
178+ if not self.token:
179+ self.token_get()
180+ url = 'users/{}'.format(username)
181+ return self.request(url)
182+
183+ def user_create(self, username, password, read_only=True):
184+ if not self.token:
185+ self.token_get()
186+ user = self.user_get(username)
187+ if user:
188+ return user
189+ url = 'users/'
190+ d = {
191+ 'username': username,
192+ 'password': password,
193+ 'full_name': username,
194+ 'email': '{}@local'.format(username),
195+ 'permissions': [],
196+ }
197+ self.request(url, method='POST', data=d)
198+ return self.user_get(username)
199+
200+ def user_permissions_clear(self, username):
201+ if not self.token:
202+ self.token_get()
203+ user = self.user_get(username)
204+ if not user:
205+ return
206+ url = 'users/{}/permissions'.format(username)
207+ self.request(url, method='DELETE')
208+
209+ def user_permissions_set(self, username, permission):
210+ if not self.token:
211+ self.token_get()
212+ user = self.user_get(username)
213+ if not user:
214+ return
215+ url = 'users/{}/permissions'.format(username)
216+ self.request(url, method='PUT', data={'permissions': permission})
217+
218+ def indexer_cluster_health(self):
219+ if not self.token:
220+ self.token_get()
221+ return self.request('system/indexer/cluster/health')
222+
223+ def indexer_failures_count(self, since):
224+ if not self.token:
225+ self.token_get()
226+ return self.request('system/indexer/failures/count', params={'since': since})
227+
228+ def journal_state(self):
229+ if not self.token:
230+ self.token_get()
231+ return self.request('system/journal')
232+
233+ def notifications_get(self):
234+ if not self.token:
235+ self.token_get()
236+ return self.request('system/notifications')
237+>>>>>>> lib/charms/layer/graylog/api.py
238diff --git a/lib/charms/layer/graylog/utils.py b/lib/charms/layer/graylog/utils.py
239index 7e9021e..29ec527 100644
240--- a/lib/charms/layer/graylog/utils.py
241+++ b/lib/charms/layer/graylog/utils.py
242@@ -5,7 +5,6 @@ from urllib.parse import urlparse
243 import netifaces
244
245 from charmhelpers.core import hookenv
246-from charms.layer import snap
247 from .version_check import is_v2
248
249
250diff --git a/lib/charms/layer/graylog/version_check.py b/lib/charms/layer/graylog/version_check.py
251index 23aafd5..9ee5adc 100644
252--- a/lib/charms/layer/graylog/version_check.py
253+++ b/lib/charms/layer/graylog/version_check.py
254@@ -1,13 +1,7 @@
255 import os
256
257-if os.environ.get('JUJU_UNIT_NAME'):
258- charm = True
259-else:
260- charm = False
261-
262-if charm:
263- from charmhelpers.core import hookenv
264- from charms.layer import snap
265+from charmhelpers.core import hookenv
266+from charms.layer import snap
267
268
269 SNAP_NAME = 'graylog'
270diff --git a/src/tests/functional/tests/__init__.py b/src/tests/functional/tests/__init__.py
271index 351a8a2..32b2652 100644
272--- a/src/tests/functional/tests/__init__.py
273+++ b/src/tests/functional/tests/__init__.py
274@@ -11,4 +11,4 @@ def _add_path(path):
275
276 built_charm = pathlib.Path(os.environ["CHARM_BUILD_DIR"]) / "graylog"
277 # Need to avoid importing c.l.graylog as it wants charmhelpers etc.
278-_add_path(str(built_charm / "lib/charms/layer/graylog"))
279+_add_path(str(built_charm / "lib"))
280diff --git a/src/tests/functional/tests/bundles/bionic-graylog2.yaml b/src/tests/functional/tests/bundles/bionic-graylog2.yaml
281index bd256db..b1318f0 100644
282--- a/src/tests/functional/tests/bundles/bionic-graylog2.yaml
283+++ b/src/tests/functional/tests/bundles/bionic-graylog2.yaml
284@@ -30,6 +30,19 @@ applications:
285 nrpe:
286 charm: cs:nrpe
287
288+ old-graylog:
289+ charm: cs:graylog-32
290+ num_units: 1
291+ series: bionic
292+
293+ elastic2:
294+ charm: cs:elasticsearch
295+ num_units: 1
296+
297+ mongo2:
298+ charm: cs:mongodb
299+ num_units: 1
300+
301 relations:
302 - - ubuntu
303 - filebeat
304@@ -43,3 +56,7 @@ relations:
305 - haproxy
306 - - graylog
307 - nrpe
308+ - - old-graylog
309+ - mongo2
310+ - - old-graylog
311+ - elastic2
312diff --git a/src/tests/functional/tests/bundles/bionic-graylog3.yaml b/src/tests/functional/tests/bundles/bionic-graylog3.yaml
313index 596cf7d..1f6020a 100644
314--- a/src/tests/functional/tests/bundles/bionic-graylog3.yaml
315+++ b/src/tests/functional/tests/bundles/bionic-graylog3.yaml
316@@ -30,6 +30,19 @@ applications:
317 nrpe:
318 charm: cs:nrpe
319
320+ old-graylog:
321+ charm: cs:graylog-32
322+ num_units: 1
323+ series: bionic
324+
325+ elastic2:
326+ charm: cs:elasticsearch
327+ num_units: 1
328+
329+ mongo2:
330+ charm: cs:mongodb
331+ num_units: 1
332+
333 relations:
334 - - ubuntu
335 - filebeat
336@@ -43,3 +56,7 @@ relations:
337 - haproxy
338 - - graylog
339 - nrpe
340+ - - old-graylog
341+ - mongo2
342+ - - old-graylog
343+ - elastic2
344diff --git a/src/tests/unit/requirements.txt b/src/tests/unit/requirements.txt
345index c465d62..d39b4a3 100644
346--- a/src/tests/unit/requirements.txt
347+++ b/src/tests/unit/requirements.txt
348@@ -5,3 +5,4 @@ netifaces
349 pytest
350 pytest-cov
351 requests
352+netifaces
353diff --git a/tests/requirements.txt b/tests/requirements.txt
354new file mode 100644
355index 0000000..27331d7
356--- /dev/null
357+++ b/tests/requirements.txt
358@@ -0,0 +1,7 @@
359+<<<<<<< tests/requirements.txt
360+=======
361+git+https://github.com/openstack-charmers/zaza.git#egg=zaza
362+charmhelpers
363+charms.reactive
364+netifaces
365+>>>>>>> tests/requirements.txt
366diff --git a/tests/shared.py b/tests/shared.py
367new file mode 100644
368index 0000000..9ea5c5a
369--- /dev/null
370+++ b/tests/shared.py
371@@ -0,0 +1,40 @@
372+from zaza import model
373+from zaza.utilities import juju
374+
375+from charms.layer.graylog.api import GraylogApi
376+
377+
378+DEFAULT_API_PORT = "9001" # Graylog 2 only
379+DEFAULT_WEB_PORT = "9000"
380+
381+
382+class BaseTest:
383+
384+ app_name = 'graylog'
385+
386+ @classmethod
387+ def is_v2(cls, app_name=app_name):
388+ config = model.get_application_config(app_name)
389+ return config['channel']['value'].startswith('2/')
390+
391+ @classmethod
392+ def get_api_port(cls):
393+ return DEFAULT_API_PORT if cls.is_v2() else DEFAULT_WEB_PORT
394+
395+ @classmethod
396+ def get_graylog_clients(cls, app_name=app_name):
397+ graylog_units = juju.get_full_juju_status().applications[app_name]["units"]
398+ graylog_addrs = [unit["public-address"] for unit in graylog_units.values()]
399+ port = cls.get_api_port()
400+ password = cls.get_graylog_admin_password()
401+ return [
402+ GraylogApi("http://{}:{}/api".format(graylog_addr, port), "admin", password)
403+ for graylog_addr in graylog_addrs
404+ ]
405+
406+ @classmethod
407+ def get_graylog_admin_password(cls, unit=None):
408+ if unit is None:
409+ unit = '{}/0'.format(cls.app_name)
410+ result = model.run_action(unit, "show-admin-password")
411+ return result.data["results"]["admin-password"]
412diff --git a/tests/test_graylog_charm.py b/tests/test_graylog_charm.py
413new file mode 100644
414index 0000000..ccd8fed
415--- /dev/null
416+++ b/tests/test_graylog_charm.py
417@@ -0,0 +1,117 @@
418+<<<<<<< tests/test_graylog_charm.py
419+=======
420+"""Encapsulate graylog testing."""
421+
422+import logging
423+import time
424+import unittest
425+
426+import zaza.model as model
427+
428+from tests.shared import BaseTest
429+
430+
431+CURL_TIMEOUT = 180
432+REQ_TIMEOUT = 12
433+
434+
435+class BaseGraylogTest(unittest.TestCase, BaseTest):
436+ """Base for Graylog charm tests."""
437+
438+ @classmethod
439+ def setUpClass(cls):
440+ super(BaseGraylogTest, cls).setUpClass()
441+ cls.model_name = model.get_juju_model()
442+ cls.application_name = "graylog"
443+ cls.lead_unit_name = model.get_lead_unit_name(
444+ cls.application_name, model_name=cls.model_name
445+ )
446+ cls.units = model.get_units(
447+ cls.application_name, model_name=cls.model_name
448+ )
449+ logging.debug("Leader unit is {}".format(cls.lead_unit_name))
450+ cls.api = cls.get_graylog_clients()[0]
451+ cls.api.req_timeout = (
452+ REQ_TIMEOUT
453+ ) # try harder to get an api connection to cater for overloaded testsystems
454+ logging.debug("API at {}".format(cls.api.base_url))
455+
456+ # account for the differences between graylog2 and 3
457+ if cls.is_v2():
458+ cls.file_field = "file"
459+ cls.expected_plugins = {
460+ "org.graylog.plugins.pipelineprocessor.ProcessorPlugin",
461+ "org.graylog.plugins.beats.BeatsInputPlugin",
462+ "org.graylog.plugins.collector.CollectorPlugin",
463+
464+ }
465+ else: # graylog 3+
466+ cls.file_field = "filebeat_log_file_path"
467+ cls.expected_plugins = {
468+ "org.graylog.aws.AWSPlugin",
469+ "org.graylog.plugins.collector.CollectorPlugin",
470+ "org.graylog.plugins.threatintel.ThreatIntelPlugin"
471+ }
472+
473+class CharmOperationTest(BaseGraylogTest):
474+ """Verify operations"""
475+
476+ def test_05_api_ready(self):
477+ """Test if the API is ready
478+
479+ Curl the api endpoint on the sentried graylog unit. We'll retry
480+ until the CURL_TIMEOUT because it may take a few seconds for the
481+ graylog systemd service to start.
482+ """
483+ curl_command = "curl http://localhost:{}".format(self.get_api_port())
484+ timeout = time.time() + CURL_TIMEOUT
485+ while time.time() < timeout:
486+ response = model.run_on_unit(self.lead_unit_name, curl_command)
487+ if response["Code"] == "0":
488+ return
489+ logging.warning(
490+ "Unexpected curl response: {}. Retrying in 30s.".format(
491+ response
492+ )
493+ )
494+ time.sleep(30)
495+
496+ # we didn't get rc=0 in the alloted time, fail the test
497+ self.fail(
498+ "Graylog didn't respond to the command \n"
499+ "'{curl_command}' as expected.\n"
500+ "Result: {result}".format(
501+ curl_command=curl_command, result=response
502+ )
503+ )
504+
505+ def test_06_elasticsearch_healthy(self):
506+ resp = self.api.indexer_cluster_health()
507+ self.assertEqual(resp["status"], "green")
508+
509+ def test_07_elasticsearch_no_faults(self):
510+ resp = self.api.indexer_failures_count("2020-01-01")
511+ self.assertEqual(int(resp["count"]), 0)
512+
513+ def test_08_plugins(self):
514+ resp = self.api.request("/system/plugins")
515+ plugin_set = set([p["unique_id"] for p in resp["plugins"]])
516+
517+ self.assertTrue(self.expected_plugins <= plugin_set)
518+
519+ def test_09_cluster(self):
520+ resp = self.api.cluster_get()
521+ self.assertGreater(len(resp.values()), 0)
522+ node = list(resp.values()).pop()
523+ self.assertTrue(node["is_processing"])
524+ self.assertEqual(node["lifecycle"], "running")
525+
526+ def test_10_keyword_search(self):
527+ params = {
528+ "query": '{}: "/var/log/cloud-init-output.log" AND ci-info'.format(self.file_field),
529+ "keyword": "4 hours ago",
530+ }
531+
532+ resp = self.api.request("/search/universal/keyword", params=params)
533+ self.assertTrue(int(resp["total_results"]) > 0)
534+>>>>>>> tests/test_graylog_charm.py
535diff --git a/tests/test_legacy.py b/tests/test_legacy.py
536new file mode 100644
537index 0000000..03294fb
538--- /dev/null
539+++ b/tests/test_legacy.py
540@@ -0,0 +1,83 @@
541+<<<<<<< tests/test_legacy.py
542+=======
543+import re
544+import unittest
545+import yaml
546+from zaza.utilities import juju
547+from zaza import model
548+
549+from tests.shared import BaseTest, DEFAULT_API_PORT, DEFAULT_WEB_PORT
550+
551+
552+class LegacyTests(unittest.TestCase, BaseTest):
553+
554+ # These tests were ported from tests/test_10_basic.py in changeset a3b54c2.
555+ # They were temporarily deleted during the conversion from Amulet to Zaza.
556+ # Aside from porting to work with libjuju and some refactoring, these tests
557+ # are largely as they were before.
558+
559+ def test_api_ready(self):
560+ """Curl the api endpoint on the graylog unit."""
561+ port = self.get_api_port()
562+ curl_command = "curl http://localhost:{} --connect-timeout 30".format(port)
563+ self.run_command("graylog/0", curl_command)
564+
565+ def test_elasticsearch_active(self):
566+ self._assert_remote_address_in_server_conf("elastic/0", "client")
567+ # This was also stuck in the same test...
568+ api = self.get_graylog_clients()[0]
569+ resp = api.indexer_cluster_health()
570+ self.assertTrue(resp["status"] == "green")
571+
572+ def test_mongodb_active(self):
573+ self._assert_remote_address_in_server_conf("mongo/0", "database")
574+ # This was also stuck in the same test...
575+ api = self.get_graylog_clients()[0]
576+ resp = api.cluster_get()
577+ self.assertTrue(resp[list(resp)[0]]["is_processing"])
578+
579+ def _assert_remote_address_in_server_conf(self, remote_unit, remote_relation_name):
580+ # This is a very simplistic, naive check, but it's equivalent to
581+ # that from tests/test_10_basic.py in a3b54c2.
582+ source_unit = "graylog/0"
583+ remote_addr = juju.get_relation_from_unit(
584+ source_unit, remote_unit, remote_relation_name
585+ )["private-address"]
586+ assert remote_addr in self.get_file_contents(
587+ source_unit, "/var/snap/graylog/common/server.conf"
588+ )
589+
590+ def test_website_active(self):
591+ data = juju.get_relation_from_unit("haproxy/0", "graylog/0", "website")
592+ self.assertEqual(data["port"], DEFAULT_WEB_PORT)
593+
594+ service_ports = {}
595+ for service in yaml.safe_load(data["all_services"]):
596+ service_ports[service["service_name"]] = service["service_port"]
597+
598+ self.assertEqual(str(service_ports["web"]), DEFAULT_WEB_PORT)
599+ if self.is_v2():
600+ self.assertEqual(str(service_ports["api"]), DEFAULT_API_PORT)
601+
602+ def run_command(self, target, command, timeout=30):
603+ result = model.run_on_unit(target, command, timeout=timeout)
604+ assert result["Code"] == "0"
605+ return result
606+
607+ def test_nrpe_config(self):
608+ cfg = self.get_file_contents(
609+ "nrpe/0", "/etc/nagios/nrpe.d/check_graylog_health.cfg"
610+ )
611+ self.assertTrue(re.search(r"command.*check_graylog_health", cfg))
612+
613+ def test_nrpe_health_check(self):
614+ cfg = self.get_file_contents(
615+ "nrpe/0", "/usr/local/lib/nagios/plugins/check_graylog_health.py"
616+ )
617+ self.assertTrue(re.search(r"CRITICAL", cfg))
618+
619+ def get_file_contents(self, unit, filename):
620+ result = self.run_command(unit, "cat '{}'".format(filename))
621+ assert result["Code"] == "0"
622+ return result["Stdout"]
623+>>>>>>> tests/test_legacy.py
624diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py
625new file mode 100644
626index 0000000..49b173a
627--- /dev/null
628+++ b/tests/test_upgrade.py
629@@ -0,0 +1,65 @@
630+import os
631+import pathlib
632+import subprocess
633+import unittest
634+import time
635+from zaza.charm_lifecycle import utils as zlc_utils
636+from zaza.utilities import juju
637+from zaza import model
638+
639+DEFAULT_API_PORT = "9001" # Graylog 2 only
640+DEFAULT_WEB_PORT = "9000"
641+
642+
643+built_charm = pathlib.Path(os.environ["CHARM_BUILD_DIR"]) / "graylog"
644+
645+
646+class UpgradeTests(unittest.TestCase):
647+
648+ """Test for charm upgrading.
649+
650+ Intent: if the charm is upgraded from an old version (especially from a
651+ GL2-only version to a GL2+3-supporting version), does it indeed still work?
652+
653+ """
654+
655+ @classmethod
656+ def setUpClass(cls):
657+ # 2019-09-26: libjuju doesn't yet support charm upgrades with
658+ # --switch <localdir> or --path <localdir>, so we need to fall back to
659+ # the command line.
660+ model_name = model.get_juju_model()
661+ subprocess.check_call([
662+ 'juju', 'upgrade-charm',
663+ '--model', model_name,
664+ 'old-graylog', '--path', built_charm])
665+
666+ # Ensure we wait enough time for the upgrade to have started.
667+ time.sleep(60)
668+
669+ test_config = zlc_utils.get_charm_config()
670+ model.wait_for_application_states(
671+ model_name,
672+ test_config.get('target_deploy_status', {}))
673+
674+ def test_api_ready(self):
675+ """Curl the api endpoint on the graylog unit."""
676+ port = self.get_api_port()
677+ curl_command = "curl http://localhost:{} --connect-timeout 30".format(port)
678+ self.run_command("graylog/0", curl_command)
679+
680+ def get_api_port(self):
681+ port = DEFAULT_API_PORT if self.is_v2() else DEFAULT_WEB_PORT
682+ return port
683+
684+ def run_command(self, target, command, timeout=30):
685+ result = model.run_on_unit(target, command, timeout=timeout)
686+ assert result["Code"] == "0"
687+ return result
688+
689+ def is_v2(self):
690+ return (
691+ juju.get_full_juju_status()
692+ .applications["graylog"]["workload-version"]
693+ .startswith("2.")
694+ )
695diff --git a/tests/tests.yaml b/tests/tests.yaml
696new file mode 100644
697index 0000000..517d340
698--- /dev/null
699+++ b/tests/tests.yaml
700@@ -0,0 +1,26 @@
701+<<<<<<< tests/tests.yaml
702+=======
703+charm_name: graylog
704+gate_bundles:
705+ - bionic-graylog2
706+ - bionic-graylog3
707+ - focal-graylog2
708+ - focal-graylog3
709+ - bionic-graylog2-ha
710+ - bionic-graylog3-ha
711+smoke_bundles:
712+ - bionic-graylog3
713+dev_bundles:
714+ - bionic-graylog3
715+tests:
716+ - tests.test_legacy.LegacyTests
717+ - tests.test_graylog_charm.CharmOperationTest
718+ - tests.test_upgrade.UpgradeTests
719+target_deploy_status:
720+ filebeat:
721+ workload-status: active
722+ workload-status-message: 'Filebeat ready.'
723+ haproxy:
724+ workload-status: unknown
725+ workload-status-message: ""
726+>>>>>>> tests/tests.yaml

Subscribers

People subscribed via source and target branches

to all changes: