Merge ~peter-sabaini/charm-graylog:add-functtests into ~graylog-charmers/charm-graylog:master

Proposed by Peter Sabaini
Status: Merged
Approved by: Jeremy Lounder
Approved revision: c8f1540a849312ab918acbc77aa40eab823efdf5
Merge reported by: Jeremy Lounder
Merged at revision: c8f1540a849312ab918acbc77aa40eab823efdf5
Proposed branch: ~peter-sabaini/charm-graylog:add-functtests
Merge into: ~graylog-charmers/charm-graylog:master
Diff against target: 281 lines (+147/-13)
11 files modified
.gitignore (+1/-0)
Makefile (+6/-1)
lib/charms/layer/graylog/api.py (+4/-2)
tests/__init__.py (+13/-0)
tests/bundles/bionic-ha.yaml (+1/-2)
tests/bundles/bionic.yaml (+1/-2)
tests/bundles/overlays/local-charm-overlay.yaml.j2 (+2/-2)
tests/requirements.txt (+1/-1)
tests/test_graylog_charm.py (+109/-0)
tests/tests.yaml (+5/-3)
tox.ini (+4/-0)
Reviewer Review Type Date Requested Status
Jeremy Lounder (community) Approve
Alvaro Uria (community) Approve
Zachary Zehring (community) Approve
Peter Sabaini (community) Needs Resubmitting
Drew Freiberger (community) Needs Fixing
Joe Guo (community) Needs Fixing
Review via email: mp+377651@code.launchpad.net

Commit message

Add functional testing

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Joe Guo (guoqiao) wrote :

Hi Peter,
I've added a few inline comments, hope it make sense:)

review: Needs Fixing
Revision history for this message
Peter Sabaini (peter-sabaini) wrote :
Download full text (6.9 KiB)

Hey Joe,

thanks for the review, some answers inline

On 15.01.20 23:14, Joe Guo wrote:
> Review: Needs Fixing
>
> Hi Peter,
> I've added a few inline comments, hope it make sense:)
>
> Diff comments:
>
>> diff --git a/Makefile b/Makefile
>> index 44e05cc..c7f5284 100644
>> --- a/Makefile
>> +++ b/Makefile
>> @@ -36,7 +36,12 @@ unittest: sysdeps lint
>>
>> .PHONY: charmbuild
>> charmbuild:
>> - charm build --output-dir $(BUILDDEST) --report
>> + charm build --output-dir $(BUILDDEST) --report --force
>> +
>> +.PHONY: functional
>> +functional: charmbuild
>> + @echo "Running functional tests tests..."
>
> redundant "tests"

Heh, indeed -- will fix

>> + @tox -e func
>>
>> .PHONY: charmrelease
>> charmrelease:
>> diff --git a/tests/bundles/overlays/local-charm-overlay.yaml.j2 b/tests/bundles/overlays/local-charm-overlay.yaml.j2
>> index b459ab1..d74aac1 100644
>> --- a/tests/bundles/overlays/local-charm-overlay.yaml.j2
>> +++ b/tests/bundles/overlays/local-charm-overlay.yaml.j2
>> @@ -1,3 +1,3 @@
>> applications:
>> - graylog:
>> - charm: ../../../graylog-built/builds/graylog
>> \ No newline at end of file
>> + {{ charm_name }}:
>> + charm: "{{ JUJU_REPOSITORY }}/builds/{{ charm_name }}"
>
> according to https://old-docs.jujucharms.com/2.5/en/reference-environment-variables, JUJU_REPOSITORY is deprecated.

JUJU_REPOSITORY is being used in the Makefile which I didn't want to touch too much at this point. I'm ok with updateing this in a 2nd iteration (I want to rip out the apt-get installs there too). Note I'm not relying on Juju honoring JUJU_REPOSITORY in any way.

>> diff --git a/tests/test_graylog_charm.py b/tests/test_graylog_charm.py
>> new file mode 100644
>> index 0000000..72702d6
>> --- /dev/null
>> +++ b/tests/test_graylog_charm.py
>> @@ -0,0 +1,112 @@
>> +"""Encapsulate graylog testing."""
>> +
>> +import logging
>> +import os
>> +import pathlib
>> +import time
>> +import sys
>> +import unittest
>> +
>> +import zaza.model as model
>> +import zaza.charm_lifecycle.utils as lifecycle_utils
>> +
>> +
>> +def _add_path(path):
>> + if path not in sys.path:
>> + sys.path.insert(1, path)
>> +
>> +
>> +built_charm = pathlib.Path(os.environ["JUJU_REPOSITORY"]) / "builds/graylog"
>> +# Need to avoid importing c.l.graylog as it wants charmhelpers etc.
>> +_add_path(str(built_charm / "lib/charms/layer/graylog"))
>
> 1. Maybe add python paths in `__init__.py`, so other test_xxx.py file can reuse that.

Makes sense, will do.

> 2. I would prefer to build charm first, and then go to the built charm dir to run tests. So the test itself doesn't need to know/hardcode the builds dir path, etc.

I would prefer to be able to update tests without rebuild. Using JUJU_REPOSITORY/builds/{charmname} is an established convention, also honored by "charm build"

> 3. while adding path, just add `lib`, then import module with `from charms.layer import graylog`, thus any layer modules are under the `charms.layer` namespace.

I unfort. can't do this, as this would trigger import of charmhelpers and require a juju runtime env, which are not available on the local system where tests are run

>> +
>> +from api import GraylogApi # noqa...

Read more...

Revision history for this message
Peter Sabaini (peter-sabaini) wrote :

Please take a look time permitting

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

Please see minor comment regarding replacing the "ubuntu/0" literal and re-allowing for --keep-model.

review: Needs Fixing
Revision history for this message
Joe Guo (guoqiao) wrote :

Just notice another minor one, other then that looks good to me.

Revision history for this message
Peter Sabaini (peter-sabaini) :
review: Needs Resubmitting
Revision history for this message
Zachary Zehring (zzehring) wrote :

A couple of small comments. After addressing, LGTM.

Revision history for this message
Peter Sabaini (peter-sabaini) wrote :

Please take a look

review: Needs Resubmitting
Revision history for this message
Zachary Zehring (zzehring) wrote :

Approving.

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

Looks good overall; I'm in favor of merging. Only one concern, and not too serious; I don't want to block merging for it.

Revision history for this message
Alvaro Uria (aluria) wrote :

Minor code style suggestions. I won't block on them either. +1.

OTOH, I think IS reviewers is not needed anymore, so feel free to mark it for mergebot to merge.

review: Approve
Revision history for this message
Peter Sabaini (peter-sabaini) wrote :

Thanks Paul, answer inline

Revision history for this message
Peter Sabaini (peter-sabaini) wrote :

Hey thanks Alvaro, I've addressed some style issues.

I'm a fan of f-string formatting but they're in Python3.6+ only, and in the interest of xenial compat I'll forgo them

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Merge proposal is approved, but source revision has changed, setting status to needs review.

Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Failed to merge change (no changes to merge), setting status to needs review.

Revision history for this message
Jeremy Lounder (jldev) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.gitignore b/.gitignore
2index 874e257..26bbf28 100644
3--- a/.gitignore
4+++ b/.gitignore
5@@ -1,6 +1,7 @@
6 *.swp
7 *~
8 .coverage
9+.idea/
10 .pytest_cache/
11 .tox/
12 .unit-state.db
13diff --git a/Makefile b/Makefile
14index 44e05cc..ded904d 100644
15--- a/Makefile
16+++ b/Makefile
17@@ -36,7 +36,12 @@ unittest: sysdeps lint
18
19 .PHONY: charmbuild
20 charmbuild:
21- charm build --output-dir $(BUILDDEST) --report
22+ charm build --output-dir $(BUILDDEST) --report --force
23+
24+.PHONY: functional
25+functional: charmbuild
26+ @echo "Running functional tests..."
27+ @tox -e func
28
29 .PHONY: charmrelease
30 charmrelease:
31diff --git a/lib/charms/layer/graylog/api.py b/lib/charms/layer/graylog/api.py
32index 3f87229..87ded7f 100644
33--- a/lib/charms/layer/graylog/api.py
34+++ b/lib/charms/layer/graylog/api.py
35@@ -32,6 +32,8 @@ class GraylogApi:
36 self.token_name = token_name
37 self.token = None
38 self.input_types = None
39+ self.req_timeout = 3
40+ self.req_retries = 4
41
42 def request(self, path, method='GET', data={}, params=None):
43 if path.startswith('/'):
44@@ -46,12 +48,12 @@ class GraylogApi:
45 if data:
46 data = json.dumps(data, indent=True)
47 tries = 0
48- while tries < 3:
49+ while tries < self.req_retries:
50 tries += 1
51 try:
52 resp = requests.api.request(method, url, auth=self.auth,
53 data=data, params=params,
54- headers=headers, timeout=3)
55+ headers=headers, timeout=self.req_timeout)
56 if resp.ok:
57 if method == 'DELETE':
58 return True
59diff --git a/tests/__init__.py b/tests/__init__.py
60new file mode 100644
61index 0000000..9abea7a
62--- /dev/null
63+++ b/tests/__init__.py
64@@ -0,0 +1,13 @@
65+import os
66+import pathlib
67+import sys
68+
69+
70+def _add_path(path):
71+ if path not in sys.path:
72+ sys.path.insert(1, path)
73+
74+
75+built_charm = pathlib.Path(os.environ["JUJU_REPOSITORY"]) / "builds/graylog"
76+# Need to avoid importing c.l.graylog as it wants charmhelpers etc.
77+_add_path(str(built_charm / "lib/charms/layer/graylog"))
78diff --git a/tests/bundles/bionic-ha.yaml b/tests/bundles/bionic-ha.yaml
79index e266cf5..516cff6 100644
80--- a/tests/bundles/bionic-ha.yaml
81+++ b/tests/bundles/bionic-ha.yaml
82@@ -10,7 +10,6 @@ applications:
83 num_units: 0
84
85 graylog:
86- charm: ../../../graylog-built/builds/graylog
87 num_units: 3
88 series: bionic
89
90@@ -36,4 +35,4 @@ relations:
91 - - graylog
92 - elastic
93 - - graylog
94- - haproxy
95\ No newline at end of file
96+ - haproxy
97diff --git a/tests/bundles/bionic.yaml b/tests/bundles/bionic.yaml
98index e196355..b646c7a 100644
99--- a/tests/bundles/bionic.yaml
100+++ b/tests/bundles/bionic.yaml
101@@ -10,7 +10,6 @@ applications:
102 num_units: 0
103
104 graylog:
105- charm: ../../../graylog-built/builds/graylog
106 num_units: 1
107 series: bionic
108
109@@ -36,4 +35,4 @@ relations:
110 - - graylog
111 - elastic
112 - - graylog
113- - haproxy
114\ No newline at end of file
115+ - haproxy
116diff --git a/tests/bundles/overlays/local-charm-overlay.yaml.j2 b/tests/bundles/overlays/local-charm-overlay.yaml.j2
117index b459ab1..d74aac1 100644
118--- a/tests/bundles/overlays/local-charm-overlay.yaml.j2
119+++ b/tests/bundles/overlays/local-charm-overlay.yaml.j2
120@@ -1,3 +1,3 @@
121 applications:
122- graylog:
123- charm: ../../../graylog-built/builds/graylog
124\ No newline at end of file
125+ {{ charm_name }}:
126+ charm: "{{ JUJU_REPOSITORY }}/builds/{{ charm_name }}"
127diff --git a/tests/requirements.txt b/tests/requirements.txt
128index 3da5fb4..b7c9112 100644
129--- a/tests/requirements.txt
130+++ b/tests/requirements.txt
131@@ -1 +1 @@
132-git+https://github.com/openstack-charmers/zaza.git#egg=zaza
133\ No newline at end of file
134+git+https://github.com/openstack-charmers/zaza.git#egg=zaza
135diff --git a/tests/test_graylog_charm.py b/tests/test_graylog_charm.py
136new file mode 100644
137index 0000000..caea523
138--- /dev/null
139+++ b/tests/test_graylog_charm.py
140@@ -0,0 +1,109 @@
141+"""Encapsulate graylog testing."""
142+
143+import logging
144+import time
145+import unittest
146+
147+import zaza.model as model
148+
149+from api import GraylogApi
150+
151+
152+CURL_TIMEOUT = 180
153+REQ_TIMEOUT = 12
154+DEFAULT_API_PORT = "9001"
155+DEFAULT_WEB_PORT = "9000"
156+
157+
158+class BaseGraylogTest(unittest.TestCase):
159+ """Base for Graylog charm tests."""
160+
161+ @classmethod
162+ def setUpClass(cls):
163+ super(BaseGraylogTest, cls).setUpClass()
164+ cls.model_name = model.get_juju_model()
165+ cls.application_name = "graylog"
166+ cls.lead_unit_name = model.get_lead_unit_name(
167+ cls.application_name, model_name=cls.model_name
168+ )
169+ cls.units = model.get_units(
170+ cls.application_name, model_name=cls.model_name
171+ )
172+ logging.debug("Leader unit is {}".format(cls.lead_unit_name))
173+ cls.graylog_ip = model.get_app_ips(cls.application_name)[0]
174+ api_url = "http://{}:{}/api".format(cls.graylog_ip, DEFAULT_API_PORT)
175+ action = model.run_action_on_leader(
176+ cls.application_name, "show-admin-password"
177+ )
178+ res = action.data["results"]
179+ cls.api = GraylogApi(api_url, "admin", res["admin-password"])
180+ cls.api.req_timeout = (
181+ REQ_TIMEOUT
182+ ) # try harder to get an api connection to cater for overloaded testsystems
183+ logging.debug("API at {}".format(api_url))
184+
185+
186+class CharmOperationTest(BaseGraylogTest):
187+ """Verify operations"""
188+
189+ def test_05_api_ready(self):
190+ """Test if the API is ready
191+
192+ Curl the api endpoint on the sentried graylog unit. We'll retry
193+ until the CURL_TIMEOUT because it may take a few seconds for the
194+ graylog systemd service to start.
195+ """
196+ curl_command = "curl http://localhost:{}".format(DEFAULT_API_PORT)
197+ timeout = time.time() + CURL_TIMEOUT
198+ while time.time() < timeout:
199+ response = model.run_on_unit(self.lead_unit_name, curl_command)
200+ if response["Code"] == "0":
201+ return
202+ logging.warning(
203+ "Unexpected curl response: {}. Retrying in 30s.".format(
204+ response
205+ )
206+ )
207+ time.sleep(30)
208+
209+ # we didn't get rc=0 in the alloted time, fail the test
210+ self.fail(
211+ "Graylog didn't respond to the command \n"
212+ "'{curl_command}' as expected.\n"
213+ "Result: {result}".format(
214+ curl_command=curl_command, result=response
215+ )
216+ )
217+
218+ def test_06_elasticsearch_healthy(self):
219+ resp = self.api.indexer_cluster_health()
220+ self.assertEqual(resp["status"], "green")
221+
222+ def test_07_elasticsearch_no_faults(self):
223+ resp = self.api.indexer_failures_count("2020-01-01")
224+ self.assertEqual(int(resp["count"]), 0)
225+
226+ def test_08_plugins(self):
227+ resp = self.api.request("/system/plugins")
228+ plugin_set = set([p["unique_id"] for p in resp["plugins"]])
229+ expected_plugins = {
230+ "org.graylog.plugins.pipelineprocessor.ProcessorPlugin",
231+ "org.graylog.plugins.beats.BeatsInputPlugin",
232+ "org.graylog.plugins.collector.CollectorPlugin",
233+ }
234+ self.assertTrue(expected_plugins <= plugin_set)
235+
236+ def test_09_cluster(self):
237+ resp = self.api.cluster_get()
238+ self.assertGreater(len(resp.values()), 0)
239+ node = list(resp.values()).pop()
240+ self.assertTrue(node["is_processing"])
241+ self.assertEqual(node["lifecycle"], "running")
242+
243+ def test_10_keyword_search(self):
244+ params = {
245+ "query": 'file: "/var/log/cloud-init-output.log" AND ci-info',
246+ "keyword": "4 hours ago",
247+ }
248+ resp = self.api.request("/search/universal/keyword", params=params)
249+ self.assertTrue(int(resp["total_results"]) > 0)
250diff --git a/tests/tests.yaml b/tests/tests.yaml
251index 1bb1e58..3a0510d 100644
252--- a/tests/tests.yaml
253+++ b/tests/tests.yaml
254@@ -6,7 +6,9 @@ smoke_bundles:
255 - bionic
256 dev_bundles:
257 - bionic
258-configure:
259- - zaza.charm_tests.noop.setup.basic_setup
260 tests:
261- - zaza.charm_tests.noop.tests.NoopTest
262+ - tests.test_graylog_charm.CharmOperationTest
263+target_deploy_status:
264+ filebeat:
265+ workload-status: active
266+ workload-status-message: 'Filebeat ready.'
267diff --git a/tox.ini b/tox.ini
268index a1f299d..a44fa4a 100644
269--- a/tox.ini
270+++ b/tox.ini
271@@ -17,6 +17,10 @@ deps = -r{toxinidir}/unit_tests/requirements.txt
272 [testenv:func]
273 commands = functest-run-suite --keep-model
274 deps = -r{toxinidir}/tests/requirements.txt
275+passenv =
276+ HOME
277+ JUJU_REPOSITORY
278+ MODEL_SETTINGS
279
280 [testenv:lint]
281 commands = flake8

Subscribers

People subscribed via source and target branches