Merge lp:~verterok/charms/trusty/apache2/tanuki_merge-upstream-r72 into lp:~tanuki/charms/trusty/apache2/trunk

Proposed by Guillermo Gonzalez
Status: Merged
Approved by: Guillermo Gonzalez
Approved revision: 70
Merged at revision: 70
Proposed branch: lp:~verterok/charms/trusty/apache2/tanuki_merge-upstream-r72
Merge into: lp:~tanuki/charms/trusty/apache2/trunk
Diff against target: 560 lines (+218/-38)
13 files modified
Makefile (+16/-5)
README.md (+6/-0)
config.yaml (+13/-0)
hooks/hooks.py (+81/-2)
hooks/install (+3/-0)
hooks/tests/test_balancer_hook.py (+13/-6)
hooks/tests/test_cert.py (+12/-0)
hooks/tests/test_config_changed.py (+7/-2)
hooks/tests/test_log_relation.py (+54/-0)
hooks/tests/test_vhost_config_relation.py (+4/-19)
metadata.yaml (+2/-0)
tests/10-bundles-test.py (+2/-2)
tests/20-mpm-test.py (+5/-2)
To merge this branch: bzr merge lp:~verterok/charms/trusty/apache2/tanuki_merge-upstream-r72
Reviewer Review Type Date Requested Status
Guillermo Gonzalez Approve
Review via email: mp+293024@code.launchpad.net

Commit message

merge upstream trunk r72

Description of the change

merge upstream trunk r72

To post a comment you must log in.
Revision history for this message
Guillermo Gonzalez (verterok) wrote :

trivial

review: Approve

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-04-26 22:51:55 +0000
4@@ -17,8 +17,16 @@
5 @(charm proof $(PWD) || [ $$? -eq 100 ]) && echo OK
6 @test `cat revision` = 0 && rm revision
7
8-.venv:
9- sudo apt-get install -y python-apt python-virtualenv python-jinja2
10+/usr/bin/apt:
11+ sudo apt-get install -y python-apt
12+
13+/usr/bin/virtualenv:
14+ sudo apt-get install -y python-virtualenv
15+
16+/usr/lib/python2.7/dist-packages/jinja2:
17+ sudo apt-get install -y python-jinja2
18+
19+.venv: /usr/bin/apt /usr/bin/virtualenv /usr/lib/python2.7/dist-packages/jinja2
20 virtualenv .venv --system-site-packages
21 .venv/bin/pip install -I nose testtools mock pyyaml
22
23@@ -28,8 +36,8 @@
24
25 lint:
26 @echo Checking for Python syntax...
27- @flake8 $(HOOKS_DIR) --ignore=E123 --exclude=$(HOOKS_DIR)/charmhelpers && echo hooks OK
28- @flake8 tests --ignore=E123 && echo tests OK
29+ @flake8 $(HOOKS_DIR) --ignore=E123,E402 --exclude=$(HOOKS_DIR)/charmhelpers && echo hooks OK
30+ @flake8 tests --ignore=E123,E402 && echo tests OK
31
32 sourcedeps: $(PWD)/config-manager.txt
33 @echo Updating source dependencies...
34@@ -42,4 +50,7 @@
35 -d hooks/charmhelpers
36 @echo Do not forget to commit the updated files if any.
37
38-.PHONY: revision proof .venv test lint sourcedeps charm-payload
39+clean:
40+ rm -rf .venv
41+
42+.PHONY: revision proof test lint sourcedeps charm-payload
43
44=== modified file 'README.md'
45--- README.md 2015-04-15 22:59:59 +0000
46+++ README.md 2016-04-26 22:51:55 +0000
47@@ -265,6 +265,12 @@
48
49 * `ports` - A space-separated list of ports that the site uses.
50
51+### Using the logs relation
52+
53+The logs relation is for use with a logging subordinate charm. The beaver
54+subordinate can be deployed and related to apache and logstash. Beaver will
55+tail apache logs and send the logs to logstash.
56+
57 ## Certs, keys and chains
58
59 `ssl_keylocation`, `ssl_certlocation` and `ssl_chainlocation` are
60
61=== modified file 'config.yaml'
62--- config.yaml 2015-08-28 12:47:41 +0000
63+++ config.yaml 2016-04-26 22:51:55 +0000
64@@ -206,3 +206,16 @@
65 type: string
66 description: Comma seperated list of OpenID providers for authentication.
67 default: ""
68+ apt-key-id:
69+ type: string
70+ default: ""
71+ description: A PGP key id. This is used with PPA and the source
72+ option to import a PGP public key for verifying repository signatures.
73+ This value must match the PPA for apt-source.
74+ apt-source:
75+ type: string
76+ default: ""
77+ description: From where to install packages. This is the PPA source line.
78+ Note that due to a bug in software-properties add-apt-repository cannot
79+ add the ondrej/apache2 ppa, so the default value here is a full
80+ sources line.
81
82=== modified file 'hooks/hooks.py'
83--- hooks/hooks.py 2015-09-23 13:57:41 +0000
84+++ hooks/hooks.py 2016-04-26 22:51:55 +0000
85@@ -27,7 +27,7 @@
86 unit_get
87 )
88 from charmhelpers.contrib.charmsupport import nrpe
89-from charmhelpers.fetch import apt_update
90+from charmhelpers.fetch import apt_update, add_source
91
92 ###############################################################################
93 # Global variables
94@@ -337,6 +337,12 @@
95
96
97 def install_hook():
98+ apt_source = config_get('apt-source') or ''
99+ apt_key_id = config_get('apt-key-id') or False
100+ if apt_source and apt_key_id:
101+ print apt_source + " and " + apt_key_id
102+ add_source(apt_source, apt_key_id)
103+ open('config.apt-source', 'w').write(apt_source)
104 if not os.path.exists(default_apache2_service_config_dir):
105 os.mkdir(default_apache2_service_config_dir, 0600)
106 apt_update(fatal=True)
107@@ -349,6 +355,15 @@
108 ensure_package_status(service_affecting_packages,
109 config_get('package_status'))
110 ensure_extra_packages()
111+ # the apache2 deb does not yet have http2 module in mods-available. Add it.
112+ open('/etc/apache2/mods-available/http2.load', 'w').write(
113+ 'LoadModule http2_module /usr/lib/apache2/modules/mod_http2.so')
114+ open('/etc/apache2/mods-available/http2.conf', 'w').write(
115+ '''<IfModule http2_module>
116+ ProtocolsHonorOrder On
117+ Protocols h2 http/1.1
118+</IfModule>
119+''')
120 return install_status
121
122
123@@ -357,7 +372,8 @@
124 if extra:
125 install_status = apt_get_install(extra)
126 if install_status == 0:
127- ensure_package_status(extra, config_get('package_status'))
128+ ensure_package_status(filter(None, extra.split(' ')),
129+ config_get('package_status'))
130
131
132 def dump_data(data2dump, log_prefix):
133@@ -642,6 +658,18 @@
134 relationship_data = {}
135 config_data = config_get()
136
137+ apt_source = config_data['apt-source']
138+ old_apt_source = ''
139+ try:
140+ old_apt_source = open('config.apt-source', 'r').read()
141+ except IOError:
142+ pass
143+ if old_apt_source != apt_source:
144+ subprocess.check_call(['add-apt-repository', '--yes', '-r',
145+ old_apt_source])
146+ add_source(apt_source, config_data['apt-key-id'])
147+ open('config.apt-source', 'w').write(apt_source)
148+
149 ensure_package_status(service_affecting_packages,
150 config_data['package_status'])
151 ensure_extra_packages()
152@@ -795,6 +823,8 @@
153 update_nrpe_checks()
154 update_etc_hosts()
155 ship_logrotate_conf()
156+ if config_get().changed('servername'):
157+ logs_relation_joined()
158
159
160 def ensure_disabled(sites):
161@@ -1014,6 +1044,53 @@
162 yaml.safe_dump(ports, pfile)
163
164
165+def get_log_files():
166+ """
167+ Read all of the apache config files from __ and get ErrorLog and AccessLog
168+ values.
169+
170+ Returns a tuple with first value list of access log files and second value
171+ list of error log files.
172+ """
173+ access_logs = []
174+ error_logs = []
175+ for protocol in ['http', 'https']:
176+ vhost_name = '%s_%s' % (config_get()['servername'], protocol)
177+ vhost_file = site_filename(vhost_name)
178+ try:
179+ # Using read().split('\n') here to work around a mocks open_mock
180+ # inadequacy: http://bugs.python.org/issue17467
181+ for line in open(vhost_file, 'r').read().split('\n'):
182+ if 'CustomLog' in line:
183+ access_logs.append(line.split()[1])
184+ elif 'ErrorLog' in line:
185+ error_logs.append(line.split()[1])
186+ except:
187+ pass
188+ return access_logs, error_logs
189+
190+
191+def logs_relation_joined():
192+ """
193+ Sets relation value with filenames
194+ """
195+ access_log_files, error_log_files = get_log_files()
196+ log_files = access_log_files[:]
197+ log_files.extend(error_log_files)
198+ types = ['apache_access' for a in access_log_files]
199+ types.extend(['apache_error' for a in error_log_files])
200+ data = {'files': '\n'.join(log_files),
201+ 'types': '\n'.join(types),
202+ }
203+ _relation_ids = relation_ids('logs')
204+ for _relation_id in _relation_ids:
205+ log("logs-relation-joined setting relation data for {} to {}".format(
206+ _relation_id, data))
207+ relation_set(
208+ relation_id=_relation_id,
209+ relation_settings=data)
210+
211+
212 ###############################################################################
213 # Main section
214 ###############################################################################
215@@ -1053,6 +1130,8 @@
216 update_nrpe_checks()
217 elif hook_name == "vhost-config-relation-changed":
218 config_changed()
219+ elif hook_name == "logs-relation-joined":
220+ logs_relation_joined()
221 else:
222 print "Unknown hook"
223 sys.exit(1)
224
225=== modified file 'hooks/install'
226--- hooks/install 2013-10-10 22:49:28 +0000
227+++ hooks/install 2016-04-26 22:51:55 +0000
228@@ -5,5 +5,8 @@
229 juju-log 'Invoking charm-pre-install hooks'
230 [ -d exec.d ] && ( for f in exec.d/*/charm-pre-install; do [ -x $f ] && /bin/sh -c "$f"; done )
231
232+juju-log 'Ensuring python, python-apt and python-yaml are installed'
233+apt-get install -y python python-apt python-yaml
234+
235 juju-log 'Invoking python-based install hook'
236 python hooks/hooks.py install
237
238=== added symlink 'hooks/logs-relation-joined'
239=== target is u'hooks.py'
240=== modified file 'hooks/tests/test_balancer_hook.py'
241--- hooks/tests/test_balancer_hook.py 2014-12-29 22:59:20 +0000
242+++ hooks/tests/test_balancer_hook.py 2016-04-26 22:51:55 +0000
243@@ -497,6 +497,7 @@
244 if os.path.exists(self.log_path):
245 os.remove(self.log_path)
246
247+ @patch('hooks.open')
248 @patch('hooks.config_get')
249 @patch('os.path.exists')
250 @patch('os.mkdir')
251@@ -504,7 +505,8 @@
252 @patch('hooks.log', MagicMock())
253 @patch('hooks.apt_update')
254 def test_installs_hook(
255- self, apt_update, apt_get_install, mkdir, exists, config_get):
256+ self, apt_update, apt_get_install, mkdir, exists, config_get,
257+ open):
258 exists.return_value = self.not_a_dir
259 config_get.return_value = None
260 apt_get_install.return_value = 'some result'
261@@ -523,19 +525,22 @@
262 call('apache2'),
263 ])
264
265- @patch('hooks.config_get')
266+ @patch('hooks.open')
267 @patch('os.path.exists')
268 @patch('os.mkdir')
269 @patch('hooks.apt_get_install')
270 @patch('hooks.log', MagicMock())
271 @patch('hooks.apt_update')
272 def test_install_hook_installs_extra_packages(
273- self, apt_update, apt_get_install, mkdir, exists, config_get):
274+ self, apt_update, apt_get_install, mkdir, exists,
275+ open):
276 exists.return_value = self.dir_exists
277- config_get.return_value = "extra"
278+ c = {'extra_packages': 'extra', 'apt-source': '', 'apt-key-id': ''}
279+ config_get = MagicMock(wraps=c.get)
280 apt_get_install.return_value = 'some result'
281
282- result = hooks.install_hook()
283+ with patch('hooks.config_get', config_get):
284+ result = hooks.install_hook()
285
286 self.assertEqual(result, 'some result')
287 apt_get_install.assert_has_calls([
288@@ -547,6 +552,7 @@
289 call('extra'),
290 ])
291
292+ @patch('hooks.open')
293 @patch('hooks.config_get')
294 @patch('os.path.exists')
295 @patch('os.mkdir')
296@@ -554,7 +560,8 @@
297 @patch('hooks.log', MagicMock())
298 @patch('hooks.apt_update')
299 def test_doesnt_create_dir_to_install_hooks_if_not_needed(
300- self, apt_update, apt_get_install, mkdir, exists, config_get):
301+ self, apt_update, apt_get_install, mkdir, exists, config_get,
302+ open):
303 exists.return_value = self.dir_exists
304 config_get.return_value = None
305 apt_get_install.return_value = 'some result'
306
307=== modified file 'hooks/tests/test_cert.py'
308--- hooks/tests/test_cert.py 2014-05-24 06:33:34 +0000
309+++ hooks/tests/test_cert.py 2016-04-26 22:51:55 +0000
310@@ -73,6 +73,12 @@
311 def test_is_selfsigned_cert_stale_public_address_changed(
312 self, unit_get, log):
313 """With different servername, cert *should* be regenerated."""
314+ try:
315+ from pyasn1.codec.der import decoder # noqa
316+ from pyasn1_modules import rfc2459 # noqa
317+ except ImportError:
318+ # This test is utopic+ only
319+ return
320 config = {"servername": "servername"}
321 cert_file = os.path.join(self.tempdir, "cert")
322 key_file = os.path.join(self.tempdir, "key")
323@@ -86,6 +92,12 @@
324 def test_is_selfsigned_cert_stale_private_address_changed(
325 self, unit_get, log):
326 """With different servername, cert *should* be regenerated."""
327+ try:
328+ from pyasn1.codec.der import decoder # noqa
329+ from pyasn1_modules import rfc2459 # noqa
330+ except ImportError:
331+ # This test is utopic+ only
332+ return
333 config = {"servername": "servername"}
334 cert_file = os.path.join(self.tempdir, "cert")
335 key_file = os.path.join(self.tempdir, "key")
336
337=== modified file 'hooks/tests/test_config_changed.py'
338--- hooks/tests/test_config_changed.py 2015-09-23 13:45:57 +0000
339+++ hooks/tests/test_config_changed.py 2016-04-26 22:51:55 +0000
340@@ -4,6 +4,10 @@
341 import shutil
342 import tempfile
343 import os
344+import sys
345+
346+sys.path.insert(0, os.path.join(os.environ['CHARM_DIR'], 'lib'))
347+from charmhelpers.core import hookenv
348
349
350 class ConfigChangedTest(TestCase):
351@@ -48,7 +52,7 @@
352 mock_conf_disable, mock_open_port, mock_close_port, mock_call,
353 mock_set_open_ports, mock_get_open_ports, mock_update_etc_hosts):
354 """config-changed hook: Site directories should be empty."""
355- mock_config_get.return_value = {
356+ mock_config_get.return_value = hookenv.Config({
357 "ssl_cert": "",
358 "ssl_key": "",
359 "package_status": "",
360@@ -64,7 +68,8 @@
361 "servername": "foobar",
362 "vhost_http_template": "",
363 "vhost_https_template": "",
364- }
365+ "apt-source": "",
366+ })
367 base = patch.object(hooks, 'default_apache_base_dir', self.dirname)
368 config22 = patch.object(hooks, 'default_apache22_config_dir',
369 "%s/conf.d" % self.dirname)
370
371=== added file 'hooks/tests/test_log_relation.py'
372--- hooks/tests/test_log_relation.py 1970-01-01 00:00:00 +0000
373+++ hooks/tests/test_log_relation.py 2016-04-26 22:51:55 +0000
374@@ -0,0 +1,54 @@
375+from testtools import TestCase
376+import mock
377+import hooks
378+hooks.log = mock.MagicMock()
379+
380+
381+class TestLogRelation(TestCase):
382+
383+ @mock.patch('hooks.config_get')
384+ def test_get_log_files_no_raise(self, config_get):
385+ 'does not raise'
386+ hooks.get_log_files()
387+
388+ @mock.patch('hooks.config_get')
389+ def test_get_log_files_parses(self, config_get):
390+ 'parses files for real'
391+ config_get.return_value = {'servername': 'hello'}
392+ m = mock.mock_open(read_data='''junk
393+ CustomLog /var/log/apache2/access.log combined\n")
394+ ErrorLog /var/log/apache2/error.log\n")
395+ junk\n"''')
396+
397+ with mock.patch('hooks.open', m, create=True):
398+ access_logs, error_logs = hooks.get_log_files()
399+ self.assertEquals('/var/log/apache2/access.log', access_logs[0])
400+ self.assertEquals('/var/log/apache2/error.log', error_logs[0])
401+
402+ @mock.patch('hooks.relation_ids')
403+ @mock.patch('hooks.relation_set')
404+ def test_log_relation_joined(self, relation_set, relation_ids):
405+ relation_ids.return_value = ['logs:1']
406+ with mock.patch('hooks.get_log_files') as get_log_files:
407+ get_log_files.return_value = ['myaccess_log'], ['myerror_log']
408+ hooks.logs_relation_joined()
409+ self.assertTrue(relation_set.called)
410+ self.assertEquals({'files': 'myaccess_log\nmyerror_log',
411+ 'types': 'apache_access\napache_error',
412+ },
413+ relation_set.call_args[1]['relation_settings'])
414+
415+ @mock.patch('hooks.relation_ids')
416+ @mock.patch('hooks.relation_set')
417+ def test_log_relation_joined_config_changed(self,
418+ relation_set,
419+ relation_ids):
420+ relation_ids.return_value = ['logs/0']
421+ with mock.patch('hooks.get_log_files') as get_log_files:
422+ get_log_files.return_value = ['myaccess_log'], ['myerror_log']
423+ hooks.logs_relation_joined()
424+ self.assertTrue(relation_set.called)
425+ self.assertEquals({'files': 'myaccess_log\nmyerror_log',
426+ 'types': 'apache_access\napache_error',
427+ },
428+ relation_set.call_args[1]['relation_settings'])
429
430=== modified file 'hooks/tests/test_vhost_config_relation.py'
431--- hooks/tests/test_vhost_config_relation.py 2014-12-29 19:37:05 +0000
432+++ hooks/tests/test_vhost_config_relation.py 2016-04-26 22:51:55 +0000
433@@ -27,23 +27,19 @@
434
435 @patch('hooks.log')
436 @patch('subprocess.call')
437- @patch('hooks.close_port')
438 def test_create_vhost_missing_template(
439- self, mock_close_port, mock_call, mock_log):
440+ self, mock_call, mock_log):
441 """Create a vhost file, check contents."""
442 hooks.create_vhost(80)
443 mock_log.assert_called_once_with(
444 "Vhost Template not provided, not configuring: %s" % 80)
445- mock_close_port.assert_called_once()
446 self.assertEqual(
447 len(os.listdir("%s/%s" % (self.dirname, "sites-available"))), 0)
448
449 @patch('hooks.log')
450 @patch('subprocess.call')
451- @patch('hooks.close_port')
452- @patch('hooks.open_port')
453 def test_create_vhost_template_through_config_no_protocol(
454- self, mock_open_port, mock_close_port, mock_call, mock_log):
455+ self, mock_call, mock_log):
456 """Create a vhost file, check contents."""
457 template = b64encode("http://{{ variable }}/")
458 config_data = {
459@@ -59,17 +55,13 @@
460 with open(filename, 'r') as file:
461 contents = file.read()
462 self.assertEqual(contents, 'http://fantastic/')
463- mock_open_port.assert_called_once()
464- mock_close_port.assert_called_once()
465 self.assertEqual(
466 len(os.listdir("%s/%s" % (self.dirname, "sites-available"))), 1)
467
468 @patch('hooks.log')
469 @patch('subprocess.call')
470- @patch('hooks.close_port')
471- @patch('hooks.open_port')
472 def test_create_vhost_template_through_config_with_protocol(
473- self, mock_open_port, mock_close_port, mock_call, mock_log):
474+ self, mock_call, mock_log):
475 """Create a vhost file, check contents."""
476 template = b64encode("http://{{ variable }}/")
477 config_data = {
478@@ -85,18 +77,13 @@
479 with open(filename, 'r') as file:
480 contents = file.read()
481 self.assertEqual(contents, 'http://fantastic/')
482- mock_open_port.assert_called_once()
483- mock_close_port.assert_called_once()
484 self.assertEqual(
485 len(os.listdir("%s/%s" % (self.dirname, "sites-available"))), 1)
486
487 @patch('hooks.log')
488 @patch('subprocess.call')
489- @patch('hooks.close_port')
490- @patch('hooks.open_port')
491 def test_create_vhost_template_directly(
492- self, mock_open_port, mock_close_port,
493- mock_call, mock_log):
494+ self, mock_call, mock_log):
495 """Create a vhost file, check contents."""
496 template = b64encode("http://{{ variable }}/")
497 config_data = {
498@@ -111,8 +98,6 @@
499 with open(filename, 'r') as file:
500 contents = file.read()
501 self.assertEqual(contents, 'http://fantastic/')
502- mock_open_port.assert_called_once()
503- mock_close_port.assert_called_once()
504 self.assertEqual(
505 len(os.listdir("%s/%s" % (self.dirname, "sites-available"))), 1)
506
507
508=== modified file 'metadata.yaml'
509--- metadata.yaml 2014-12-18 01:21:23 +0000
510+++ metadata.yaml 2016-04-26 22:51:55 +0000
511@@ -18,6 +18,8 @@
512 scope: container
513 website:
514 interface: http
515+ logs:
516+ interface: logs
517 apache-website:
518 interface: apache-website
519 scope: container
520
521=== modified file 'tests/10-bundles-test.py'
522--- tests/10-bundles-test.py 2015-03-10 10:56:19 +0000
523+++ tests/10-bundles-test.py 2016-04-26 22:51:55 +0000
524@@ -20,8 +20,8 @@
525 with open(bundle_path, 'r') as bundle_file:
526 contents = yaml.safe_load(bundle_file)
527 d.load(contents)
528- d.setup(seconds_to_wait)
529- d.sentry.wait(seconds_to_wait)
530+ d.setup(timeout=seconds_to_wait)
531+ d.sentry.wait(timeout=seconds_to_wait)
532 cls.d = d
533
534 def test_deployed(self):
535
536=== modified file 'tests/20-mpm-test.py'
537--- tests/20-mpm-test.py 2015-03-10 10:56:19 +0000
538+++ tests/20-mpm-test.py 2016-04-26 22:51:55 +0000
539@@ -4,6 +4,8 @@
540 import unittest
541 import amulet
542
543+seconds_to_wait = 600
544+
545
546 class BundleTest(unittest.TestCase):
547 """ Create a class for testing the charm in the unit test framework. """
548@@ -12,9 +14,10 @@
549 """ Set up an amulet deployment using the bundle. """
550 d = amulet.Deployment(series='trusty')
551 d.add('apache2', os.path.join(os.path.dirname(__file__), os.pardir))
552- d.setup(180)
553+ d.setup(timeout=seconds_to_wait)
554+ d.sentry.wait(timeout=seconds_to_wait)
555 cls.d = d
556- cls.unit = d.sentry.unit['apache2/0']
557+ cls.unit = d.sentry['apache2'][0]
558 output, code = cls.unit.run('curl localhost')
559
560 def assert_mpm(self, mpm):

Subscribers

People subscribed via source and target branches