Merge lp:~evarlast/charms/trusty/apache2/add-logs-interface into lp:charms/trusty/apache2

Proposed by Jay R. Wren
Status: Merged
Merged at revision: 68
Proposed branch: lp:~evarlast/charms/trusty/apache2/add-logs-interface
Merge into: lp:charms/trusty/apache2
Diff against target: 341 lines (+141/-24)
8 files modified
Makefile (+3/-3)
README.md (+6/-0)
hooks/hooks.py (+52/-0)
hooks/tests/test_cert.py (+12/-0)
hooks/tests/test_config_changed.py (+6/-2)
hooks/tests/test_log_relation.py (+56/-0)
hooks/tests/test_vhost_config_relation.py (+4/-19)
metadata.yaml (+2/-0)
To merge this branch: bzr merge lp:~evarlast/charms/trusty/apache2/add-logs-interface
Reviewer Review Type Date Requested Status
Jay R. Wren (community) Needs Resubmitting
Andrew McLeod (community) Needs Fixing
Review Queue (community) automated testing Needs Fixing
charmers Pending
Review via email: mp+278222@code.launchpad.net

Description of the change

Add logs relation.

This exposes log files written by apache via the logs relation. Logger services such as logstash can be related to this service and this will configure the logger to read these files.

To post a comment you must log in.
70. By Jay R. Wren

merge parent

Revision history for this message
Review Queue (review-queue) wrote :

This item has failed automated testing! Results available here http://juju-ci.vapour.ws:8080/job/charm-bundle-test-lxc/1485/

review: Needs Fixing (automated testing)
Revision history for this message
Review Queue (review-queue) wrote :

This item has failed automated testing! Results available here http://juju-ci.vapour.ws:8080/job/charm-bundle-test-aws/1468/

review: Needs Fixing (automated testing)
71. By Jay R. Wren

fix broken tests

Revision history for this message
Review Queue (review-queue) wrote :

This item has failed automated testing! Results available here http://juju-ci.vapour.ws:8080/job/charm-bundle-test-lxc/1535/

review: Needs Fixing (automated testing)
Revision history for this message
Review Queue (review-queue) wrote :

This item has failed automated testing! Results available here http://juju-ci.vapour.ws:8080/job/charm-bundle-test-aws/1518/

review: Needs Fixing (automated testing)
Revision history for this message
Andrew McLeod (admcleod) wrote :

Hi Jay,

As per my comments in your 'apt source' merge request, this one is failing with the following:

http://pastebin.ubuntu.com/13898257/

Andrew

review: Needs Fixing
72. By Jay R. Wren

make utopic+ tests pass on trusty

Revision history for this message
Jay R. Wren (evarlast) wrote :

I don't see how these tests EVER passed this criteria. Evidence to the contrary is appreciated.

I've disabled the tests on trusty as the hook code in the charm doesn't work in trusty and is documented as such.

Please consider for merging again.

review: Needs Resubmitting
Revision history for this message
Cory Johns (johnsca) wrote :

Jay,

Thanks for addressing those test failures. I think your solution is reasonable, and the tests now pass for me. I think that this mostly looks good, except for two small items.

First, I think there's a corner case in how you write out to the logs relation, that I mentioned in my diff comment below. I think just dropping the call to relation_ids() entirely should resolve it without issue.

Second, I would like to see some documentation in the README about how this is supposed to be used. You mention logstash in the MR, above, but that charm doesn't support this relation directly. I'm guessing that you intend this to be used via the beaver charm (e.g., https://jujucharms.com/u/yellow/beaver/trusty/0) because that has an interface that seems to match up, but it's not at all clear.

With those two small changes, I would give this my +1.

73. By Jay R. Wren

use relation_ids always, and add a section about logs relation to the README

Revision history for this message
Jay R. Wren (evarlast) wrote :

I have updated with those two things.

Revision history for this message
Cory Johns (johnsca) wrote :

Jay,

Thank you for addressing those items. This has now been merged, and should be ingested into the charm store within the hour.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2015-03-10 10:56:19 +0000
3+++ Makefile 2016-01-15 23:00:04 +0000
4@@ -18,7 +18,7 @@
5 @test `cat revision` = 0 && rm revision
6
7 .venv:
8- sudo apt-get install -y python-apt python-virtualenv python-jinja2
9+ sudo apt-get install -y python-apt python-virtualenv python-jinja2 python-openssl
10 virtualenv .venv --system-site-packages
11 .venv/bin/pip install -I nose testtools mock pyyaml
12
13@@ -28,8 +28,8 @@
14
15 lint:
16 @echo Checking for Python syntax...
17- @flake8 $(HOOKS_DIR) --ignore=E123 --exclude=$(HOOKS_DIR)/charmhelpers && echo hooks OK
18- @flake8 tests --ignore=E123 && echo tests OK
19+ @flake8 $(HOOKS_DIR) --ignore=E123,E402 --exclude=$(HOOKS_DIR)/charmhelpers && echo hooks OK
20+ @flake8 tests --ignore=E123,E402 && echo tests OK
21
22 sourcedeps: $(PWD)/config-manager.txt
23 @echo Updating source dependencies...
24
25=== modified file 'README.md'
26--- README.md 2015-04-15 22:59:59 +0000
27+++ README.md 2016-01-15 23:00:04 +0000
28@@ -265,6 +265,12 @@
29
30 * `ports` - A space-separated list of ports that the site uses.
31
32+### Using the logs relation
33+
34+The logs relation is for use with a logging subordinate charm. The beaver
35+subordinate can be deployed and related to apache and logstash. Beaver will
36+tail apache logs and send the logs to logstash.
37+
38 ## Certs, keys and chains
39
40 `ssl_keylocation`, `ssl_certlocation` and `ssl_chainlocation` are
41
42=== modified file 'hooks/hooks.py'
43--- hooks/hooks.py 2015-04-15 20:23:01 +0000
44+++ hooks/hooks.py 2016-01-15 23:00:04 +0000
45@@ -22,6 +22,7 @@
46 relations_of_type,
47 relation_set,
48 relation_ids,
49+ relation_id,
50 unit_get
51 )
52 from charmhelpers.contrib.charmsupport import nrpe
53@@ -737,6 +738,8 @@
54 ensure_ports(all_ports)
55 update_nrpe_checks()
56 ship_logrotate_conf()
57+ if config_get().changed('servername'):
58+ logs_relation_joined()
59
60
61 def ensure_disabled(sites):
62@@ -956,6 +959,53 @@
63 yaml.safe_dump(ports, pfile)
64
65
66+def get_log_files():
67+ """
68+ Read all of the apache config files from __ and get ErrorLog and AccessLog
69+ values.
70+
71+ Returns a tuple with first value list of access log files and second value
72+ list of error log files.
73+ """
74+ access_logs = []
75+ error_logs = []
76+ for protocol in ['http', 'https']:
77+ vhost_name = '%s_%s' % (config_get()['servername'], protocol)
78+ vhost_file = site_filename(vhost_name)
79+ try:
80+ # Using read().split('\n') here to work around a mocks open_mock
81+ # inadequacy: http://bugs.python.org/issue17467
82+ for line in open(vhost_file, 'r').read().split('\n'):
83+ if 'CustomLog' in line:
84+ access_logs.append(line.split()[1])
85+ elif 'ErrorLog' in line:
86+ error_logs.append(line.split()[1])
87+ except:
88+ pass
89+ return access_logs, error_logs
90+
91+
92+def logs_relation_joined():
93+ """
94+ Sets relation value with filenames
95+ """
96+ access_log_files, error_log_files = get_log_files()
97+ log_files = access_log_files[:]
98+ log_files.extend(error_log_files)
99+ types = ['apache_access' for a in access_log_files]
100+ types.extend(['apache_error' for a in error_log_files])
101+ data = {'files': '\n'.join(log_files),
102+ 'types': '\n'.join(types),
103+ }
104+ _relation_ids = relation_ids('logs')
105+ for _relation_id in _relation_ids:
106+ log("logs-relation-joined setting relation data for {} to {}".format(
107+ _relation_id, data))
108+ relation_set(
109+ relation_id=_relation_id,
110+ relation_settings=data)
111+
112+
113 ###############################################################################
114 # Main section
115 ###############################################################################
116@@ -995,6 +1045,8 @@
117 update_nrpe_checks()
118 elif hook_name == "vhost-config-relation-changed":
119 config_changed()
120+ elif hook_name == "logs-relation-joined":
121+ logs_relation_joined()
122 else:
123 print "Unknown hook"
124 sys.exit(1)
125
126=== added symlink 'hooks/logs-relation-joined'
127=== target is u'hooks.py'
128=== modified file 'hooks/tests/test_cert.py'
129--- hooks/tests/test_cert.py 2014-05-24 06:33:34 +0000
130+++ hooks/tests/test_cert.py 2016-01-15 23:00:04 +0000
131@@ -73,6 +73,12 @@
132 def test_is_selfsigned_cert_stale_public_address_changed(
133 self, unit_get, log):
134 """With different servername, cert *should* be regenerated."""
135+ try:
136+ from pyasn1.codec.der import decoder
137+ from pyasn1_modules import rfc2459
138+ except ImportError:
139+ # This test is utopic+ only
140+ return
141 config = {"servername": "servername"}
142 cert_file = os.path.join(self.tempdir, "cert")
143 key_file = os.path.join(self.tempdir, "key")
144@@ -86,6 +92,12 @@
145 def test_is_selfsigned_cert_stale_private_address_changed(
146 self, unit_get, log):
147 """With different servername, cert *should* be regenerated."""
148+ try:
149+ from pyasn1.codec.der import decoder
150+ from pyasn1_modules import rfc2459
151+ except ImportError:
152+ # This test is utopic+ only
153+ return
154 config = {"servername": "servername"}
155 cert_file = os.path.join(self.tempdir, "cert")
156 key_file = os.path.join(self.tempdir, "key")
157
158=== modified file 'hooks/tests/test_config_changed.py'
159--- hooks/tests/test_config_changed.py 2015-04-15 20:23:01 +0000
160+++ hooks/tests/test_config_changed.py 2016-01-15 23:00:04 +0000
161@@ -4,6 +4,10 @@
162 import shutil
163 import tempfile
164 import os
165+import sys
166+
167+sys.path.insert(0, os.path.join(os.environ['CHARM_DIR'], 'lib'))
168+from charmhelpers.core import hookenv
169
170
171 class ConfigChangedTest(TestCase):
172@@ -47,7 +51,7 @@
173 mock_conf_disable, mock_open_port, mock_close_port, mock_call,
174 mock_set_open_ports, mock_get_open_ports):
175 """config-changed hook: Site directories should be empty."""
176- mock_config_get.return_value = {
177+ mock_config_get.return_value = hookenv.Config({
178 "ssl_cert": "",
179 "ssl_key": "",
180 "package_status": "",
181@@ -63,7 +67,7 @@
182 "servername": "foobar",
183 "vhost_http_template": "",
184 "vhost_https_template": "",
185- }
186+ })
187 base = patch.object(hooks, 'default_apache_base_dir', self.dirname)
188 config22 = patch.object(hooks, 'default_apache22_config_dir',
189 "%s/conf.d" % self.dirname)
190
191=== added file 'hooks/tests/test_log_relation.py'
192--- hooks/tests/test_log_relation.py 1970-01-01 00:00:00 +0000
193+++ hooks/tests/test_log_relation.py 2016-01-15 23:00:04 +0000
194@@ -0,0 +1,56 @@
195+from testtools import TestCase
196+import mock
197+import hooks
198+hooks.log = mock.MagicMock()
199+
200+
201+class TestLogRelation(TestCase):
202+
203+ @mock.patch('hooks.config_get')
204+ def test_get_log_files_no_raise(self, config_get):
205+ 'does not raise'
206+ hooks.get_log_files()
207+
208+ @mock.patch('hooks.config_get')
209+ def test_get_log_files_parses(self, config_get):
210+ 'parses files for real'
211+ config_get.return_value = {'servername': 'hello'}
212+ m = mock.mock_open(read_data='''junk
213+ CustomLog /var/log/apache2/access.log combined\n")
214+ ErrorLog /var/log/apache2/error.log\n")
215+ junk\n"''')
216+
217+ with mock.patch('hooks.open', m, create=True):
218+ access_logs, error_logs = hooks.get_log_files()
219+ self.assertEquals('/var/log/apache2/access.log', access_logs[0])
220+ self.assertEquals('/var/log/apache2/error.log', error_logs[0])
221+
222+ @mock.patch('hooks.relation_id')
223+ @mock.patch('hooks.relation_set')
224+ def test_log_relation_joined(self, relation_set, relation_id):
225+ with mock.patch('hooks.get_log_files') as get_log_files:
226+ get_log_files.return_value = ['myaccess_log'], ['myerror_log']
227+ hooks.logs_relation_joined()
228+ self.assertTrue(relation_set.called)
229+ self.assertEquals({'files': 'myaccess_log\nmyerror_log',
230+ 'types': 'apache_access\napache_error',
231+ },
232+ relation_set.call_args[1]['relation_settings'])
233+
234+ @mock.patch('hooks.relation_ids')
235+ @mock.patch('hooks.relation_id')
236+ @mock.patch('hooks.relation_set')
237+ def test_log_relation_joined_config_changed(self,
238+ relation_set,
239+ relation_id,
240+ relation_ids):
241+ relation_id.return_value = None
242+ relation_ids.return_value = ['logs/0']
243+ with mock.patch('hooks.get_log_files') as get_log_files:
244+ get_log_files.return_value = ['myaccess_log'], ['myerror_log']
245+ hooks.logs_relation_joined()
246+ self.assertTrue(relation_set.called)
247+ self.assertEquals({'files': 'myaccess_log\nmyerror_log',
248+ 'types': 'apache_access\napache_error',
249+ },
250+ relation_set.call_args[1]['relation_settings'])
251
252=== modified file 'hooks/tests/test_vhost_config_relation.py'
253--- hooks/tests/test_vhost_config_relation.py 2014-12-29 19:37:05 +0000
254+++ hooks/tests/test_vhost_config_relation.py 2016-01-15 23:00:04 +0000
255@@ -27,23 +27,19 @@
256
257 @patch('hooks.log')
258 @patch('subprocess.call')
259- @patch('hooks.close_port')
260 def test_create_vhost_missing_template(
261- self, mock_close_port, mock_call, mock_log):
262+ self, mock_call, mock_log):
263 """Create a vhost file, check contents."""
264 hooks.create_vhost(80)
265 mock_log.assert_called_once_with(
266 "Vhost Template not provided, not configuring: %s" % 80)
267- mock_close_port.assert_called_once()
268 self.assertEqual(
269 len(os.listdir("%s/%s" % (self.dirname, "sites-available"))), 0)
270
271 @patch('hooks.log')
272 @patch('subprocess.call')
273- @patch('hooks.close_port')
274- @patch('hooks.open_port')
275 def test_create_vhost_template_through_config_no_protocol(
276- self, mock_open_port, mock_close_port, mock_call, mock_log):
277+ self, mock_call, mock_log):
278 """Create a vhost file, check contents."""
279 template = b64encode("http://{{ variable }}/")
280 config_data = {
281@@ -59,17 +55,13 @@
282 with open(filename, 'r') as file:
283 contents = file.read()
284 self.assertEqual(contents, 'http://fantastic/')
285- mock_open_port.assert_called_once()
286- mock_close_port.assert_called_once()
287 self.assertEqual(
288 len(os.listdir("%s/%s" % (self.dirname, "sites-available"))), 1)
289
290 @patch('hooks.log')
291 @patch('subprocess.call')
292- @patch('hooks.close_port')
293- @patch('hooks.open_port')
294 def test_create_vhost_template_through_config_with_protocol(
295- self, mock_open_port, mock_close_port, mock_call, mock_log):
296+ self, mock_call, mock_log):
297 """Create a vhost file, check contents."""
298 template = b64encode("http://{{ variable }}/")
299 config_data = {
300@@ -85,18 +77,13 @@
301 with open(filename, 'r') as file:
302 contents = file.read()
303 self.assertEqual(contents, 'http://fantastic/')
304- mock_open_port.assert_called_once()
305- mock_close_port.assert_called_once()
306 self.assertEqual(
307 len(os.listdir("%s/%s" % (self.dirname, "sites-available"))), 1)
308
309 @patch('hooks.log')
310 @patch('subprocess.call')
311- @patch('hooks.close_port')
312- @patch('hooks.open_port')
313 def test_create_vhost_template_directly(
314- self, mock_open_port, mock_close_port,
315- mock_call, mock_log):
316+ self, mock_call, mock_log):
317 """Create a vhost file, check contents."""
318 template = b64encode("http://{{ variable }}/")
319 config_data = {
320@@ -111,8 +98,6 @@
321 with open(filename, 'r') as file:
322 contents = file.read()
323 self.assertEqual(contents, 'http://fantastic/')
324- mock_open_port.assert_called_once()
325- mock_close_port.assert_called_once()
326 self.assertEqual(
327 len(os.listdir("%s/%s" % (self.dirname, "sites-available"))), 1)
328
329
330=== modified file 'metadata.yaml'
331--- metadata.yaml 2014-12-18 01:21:23 +0000
332+++ metadata.yaml 2016-01-15 23:00:04 +0000
333@@ -18,6 +18,8 @@
334 scope: container
335 website:
336 interface: http
337+ logs:
338+ interface: logs
339 apache-website:
340 interface: apache-website
341 scope: container

Subscribers

People subscribed via source and target branches

to all changes: