Merge lp:~abentley/charms/trusty/apache2/apache2-ports into lp:charms/trusty/apache2

Proposed by Aaron Bentley
Status: Merged
Approved by: David Britton
Approved revision: 63
Merged at revision: 59
Proposed branch: lp:~abentley/charms/trusty/apache2/apache2-ports
Merge into: lp:charms/trusty/apache2
Diff against target: 287 lines (+133/-21)
5 files modified
hooks/hooks.py (+56/-16)
hooks/tests/test_balancer_hook.py (+3/-3)
hooks/tests/test_config_changed.py (+4/-1)
hooks/tests/test_hooks.py (+65/-0)
hooks/tests/test_vhost_config_relation.py (+5/-1)
To merge this branch: bzr merge lp:~abentley/charms/trusty/apache2/apache2-ports
Reviewer Review Type Date Requested Status
David Britton (community) Approve
Adam Israel (community) Approve
Review Queue (community) automated testing Needs Fixing
Review via email: mp+245740@code.launchpad.net

Commit message

Unify port handling.

Description of the change

This branch unifies open-port/close-port handling. It is a prerequisite for apache2-website-interface support, which I will introduce in a follow-on branch.

Multiple configuration options may refer to the same port. For example, a vhost_http_template might refer to port 80 and so might a vhost-config relation. But they can have conflicting ideas about whether a port should be open. If the vhost-config has an empty template, port 80 will be closed, even though the vhost_http_template wants it open. And of course, there can be multiple vhost-configs with their own conflicting opinions.

This code unifies the port handling, so that it is done in a single place. If any configuration mechanism wants a paticular port open, it is open. Otherwise it is closed. It also records the current list of ports, so that future hook runs can detect when a port has changed, and close the old port.

To post a comment you must log in.
Revision history for this message
Review Queue (review-queue) wrote :

This items has failed automated testing! Results available here http://reports.vapour.ws/charm-tests/charm-bundle-test-10870-results

review: Needs Fixing (automated testing)
Revision history for this message
Aaron Bentley (abentley) wrote :

The automated test failures are a pre-existing issue:
http://reports.vapour.ws/charm-tests/charm-bundle-test-10700-results

They are not caused by my changes.

Revision history for this message
Adam Israel (aisrael) wrote :

Hi Aaron,

I had the opportunity to review this MP today, and I agree that the failing tests are unrelated to your changes. LGTM. +1

review: Approve
Revision history for this message
David Britton (dpb) wrote :

This looks great, Aaron.

Thanks! +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'hooks/hooks.py'
--- hooks/hooks.py 2014-11-20 00:06:41 +0000
+++ hooks/hooks.py 2015-01-07 14:04:20 +0000
@@ -1,5 +1,6 @@
1#!/usr/bin/env python1#!/usr/bin/env python
22
3import errno
3import os4import os
4import re5import re
5import socket6import socket
@@ -519,11 +520,10 @@
519 """520 """
520 if protocol is None:521 if protocol is None:
521 protocol = str(port)522 protocol = str(port)
522 close_port(port)
523 if template_str is None:523 if template_str is None:
524 if not config_key or not config_data[config_key]:524 if not config_key or not config_data[config_key]:
525 log("Vhost Template not provided, not configuring: %s" % port)525 log("Vhost Template not provided, not configuring: %s" % port)
526 return526 return False
527 template_str = config_data[config_key]527 template_str = config_data[config_key]
528 from jinja2 import Template528 from jinja2 import Template
529 template = Template(str(base64.b64decode(template_str)))529 template = Template(str(base64.b64decode(template_str)))
@@ -533,8 +533,8 @@
533 log("Writing file %s with config and relation data" % vhost_file)533 log("Writing file %s with config and relation data" % vhost_file)
534 with open(vhost_file, 'w') as vhost:534 with open(vhost_file, 'w') as vhost:
535 vhost.write(str(template.render(template_data)))535 vhost.write(str(template.render(template_data)))
536 open_port(port)
537 subprocess.call(['/usr/sbin/a2ensite', vhost_name])536 subprocess.call(['/usr/sbin/a2ensite', vhost_name])
537 return True
538538
539539
540def config_changed():540def config_changed():
@@ -572,13 +572,15 @@
572 create_security()572 create_security()
573573
574 ports = {'http': 80, 'https': 443}574 ports = {'http': 80, 'https': 443}
575 all_ports = set()
575 for protocol, port in ports.iteritems():576 for protocol, port in ports.iteritems():
576 create_vhost(577 if create_vhost(
577 port,578 port,
578 protocol=protocol,579 protocol=protocol,
579 config_key="vhost_%s_template" % protocol,580 config_key="vhost_%s_template" % protocol,
580 config_data=config_data,581 config_data=config_data,
581 relationship_data=relationship_data)582 relationship_data=relationship_data):
583 all_ports.add(port)
582584
583 cert_file = _get_cert_file_location(config_data)585 cert_file = _get_cert_file_location(config_data)
584 key_file = _get_key_file_location(config_data)586 key_file = _get_key_file_location(config_data)
@@ -686,7 +688,8 @@
686 f.write('\n')688 f.write('\n')
687 os.chmod(key_file, 0444)689 os.chmod(key_file, 0444)
688690
689 update_vhost_config_relation()691 all_ports.update(update_vhost_config_relation())
692 ensure_ports(all_ports)
690 update_nrpe_checks()693 update_nrpe_checks()
691 ship_logrotate_conf()694 ship_logrotate_conf()
692695
@@ -696,10 +699,11 @@
696 Update the vhost file and include the certificate in the relation699 Update the vhost file and include the certificate in the relation
697 if it is self-signed.700 if it is self-signed.
698 """701 """
702 vhost_ports = set()
699 relation_data = relations_of_type("vhost-config")703 relation_data = relations_of_type("vhost-config")
700 config_data = config_get()704 config_data = config_get()
701 if relation_data is None:705 if relation_data is None:
702 return706 return vhost_ports
703707
704 for unit_data in relation_data:708 for unit_data in relation_data:
705 if "vhosts" in unit_data:709 if "vhosts" in unit_data:
@@ -711,11 +715,13 @@
711 try:715 try:
712 vhosts = yaml.safe_load(unit_data["vhosts"])716 vhosts = yaml.safe_load(unit_data["vhosts"])
713 for vhost in vhosts:717 for vhost in vhosts:
714 create_vhost(718 port = vhost["port"]
715 vhost["port"],719 if create_vhost(
716 template_str=vhost["template"],720 port,
717 config_data=config_data,721 template_str=vhost["template"],
718 relationship_data=all_relation_data)722 config_data=config_data,
723 relationship_data=all_relation_data):
724 vhost_ports.add(port)
719 except Exception as e:725 except Exception as e:
720 log("Error reading configuration data from relation! %s" % e)726 log("Error reading configuration data from relation! %s" % e)
721 raise727 raise
@@ -736,6 +742,7 @@
736 vhost_relation_settings["ssl_cert"] = cert742 vhost_relation_settings["ssl_cert"] = cert
737 for id in relation_ids("vhost-config"):743 for id in relation_ids("vhost-config"):
738 relation_set(relation_id=id, relation_settings=vhost_relation_settings)744 relation_set(relation_id=id, relation_settings=vhost_relation_settings)
745 return vhost_ports
739746
740747
741def start_hook():748def start_hook():
@@ -768,6 +775,39 @@
768 'hostname=%s' % my_host])775 'hostname=%s' % my_host])
769776
770777
778def ensure_ports(ports):
779 """Ensure that only the desired ports are open."""
780 open_ports = set(get_open_ports())
781 ports = set(ports)
782 wanted_closed = ports.difference(open_ports)
783 for port in sorted(wanted_closed):
784 open_port(port)
785 unwanted_open = open_ports.difference(ports)
786 for port in sorted(unwanted_open):
787 close_port(port)
788 set_open_ports(list(sorted(ports)))
789
790
791def get_open_ports():
792 """Get the list of open ports from the standard file."""
793 try:
794 pfile = open(os.path.join(os.environ['CHARM_DIR'], 'ports.yaml'))
795 except IOError as e:
796 if e.errno == errno.ENOENT:
797 return []
798 else:
799 raise
800 with pfile:
801 return yaml.safe_load(pfile)
802
803
804def set_open_ports(ports):
805 """Write the list of open ports to the standard file."""
806 ports_path = os.path.join(os.environ['CHARM_DIR'], 'ports.yaml')
807 with open(ports_path, 'w') as pfile:
808 yaml.safe_dump(ports, pfile)
809
810
771###############################################################################811###############################################################################
772# Main section812# Main section
773###############################################################################813###############################################################################
774814
=== modified file 'hooks/tests/test_balancer_hook.py'
--- hooks/tests/test_balancer_hook.py 2014-10-13 07:55:57 +0000
+++ hooks/tests/test_balancer_hook.py 2015-01-07 14:04:20 +0000
@@ -207,12 +207,12 @@
207 check_output.assert_called_with('foo', 1, 2, bar='baz')207 check_output.assert_called_with('foo', 1, 2, bar='baz')
208208
209 @patch('subprocess.check_output')209 @patch('subprocess.check_output')
210 @patch('sys.stdout.write')210 @patch('sys.stdout')
211 def test_prints_and_reraises_run_error(self, write, check_output):211 def test_prints_and_reraises_run_error(self, out, check_output):
212 check_output.side_effect = RuntimeError('some error')212 check_output.side_effect = RuntimeError('some error')
213213
214 self.assertRaises(RuntimeError, hooks.run, 'some command')214 self.assertRaises(RuntimeError, hooks.run, 'some command')
215 self.assertEqual(write.mock_calls, [215 self.assertEqual(out.write.mock_calls, [
216 call('some error'),216 call('some error'),
217 call('\n'),217 call('\n'),
218 ])218 ])
219219
=== modified file 'hooks/tests/test_config_changed.py'
--- hooks/tests/test_config_changed.py 2014-05-21 03:24:19 +0000
+++ hooks/tests/test_config_changed.py 2015-01-07 14:04:20 +0000
@@ -16,6 +16,8 @@
16 if os.path.exists(self.dirname):16 if os.path.exists(self.dirname):
17 shutil.rmtree(self.dirname)17 shutil.rmtree(self.dirname)
1818
19 @patch('hooks.get_open_ports')
20 @patch('hooks.set_open_ports')
19 @patch('hooks.subprocess.call')21 @patch('hooks.subprocess.call')
20 @patch('hooks.close_port')22 @patch('hooks.close_port')
21 @patch('hooks.open_port')23 @patch('hooks.open_port')
@@ -41,7 +43,8 @@
41 mock_create_mpm_workerfile, mock_create_security, mock_run,43 mock_create_mpm_workerfile, mock_create_security, mock_run,
42 mock_update_nrpe_checks, mock_ship_logrotate_conf,44 mock_update_nrpe_checks, mock_ship_logrotate_conf,
43 mock_ensure_package_status, mock_ensure_extra_packages,45 mock_ensure_package_status, mock_ensure_extra_packages,
44 mock_conf_disable, mock_open_port, mock_close_port, mock_call):46 mock_conf_disable, mock_open_port, mock_close_port, mock_call,
47 mock_set_open_ports, mock_get_open_ports):
45 """config-changed hook: Site directories should be empty."""48 """config-changed hook: Site directories should be empty."""
46 mock_config_get.return_value = {49 mock_config_get.return_value = {
47 "ssl_cert": "",50 "ssl_cert": "",
4851
=== modified file 'hooks/tests/test_hooks.py'
--- hooks/tests/test_hooks.py 2014-05-20 16:54:57 +0000
+++ hooks/tests/test_hooks.py 2015-01-07 14:04:20 +0000
@@ -1,7 +1,27 @@
1from contextlib import contextmanager
2import os.path
3from tempfile import mkdtemp
4from shutil import rmtree
5
1from testtools import TestCase6from testtools import TestCase
7from mock import (
8 call,
9 patch,
10)
11import yaml
12
2import hooks13import hooks
314
415
16@contextmanager
17def temp_dir():
18 directory = mkdtemp()
19 try:
20 yield directory
21 finally:
22 rmtree(directory)
23
24
5class HooksTest(TestCase):25class HooksTest(TestCase):
6 def test__get_key_file_location_empty(self):26 def test__get_key_file_location_empty(self):
7 """No ssl_keylocation, expect None."""27 """No ssl_keylocation, expect None."""
@@ -32,3 +52,48 @@
32 """ssl_keylocation, expect correct path."""52 """ssl_keylocation, expect correct path."""
33 self.assertEqual(hooks._get_chain_file_location(53 self.assertEqual(hooks._get_chain_file_location(
34 {"ssl_chainlocation": "foo"}), "/etc/ssl/certs/foo")54 {"ssl_chainlocation": "foo"}), "/etc/ssl/certs/foo")
55
56
57class TestEnsurePorts(TestCase):
58
59 def test_ensure_ports(self):
60 with patch('hooks.open_port') as hop_mock:
61 with patch('hooks.set_open_ports') as hsop_mock:
62 with patch('hooks.get_open_ports', return_value=[]):
63 hooks.ensure_ports([80, 81])
64 self.assertEqual(hop_mock.mock_calls, [call(80), call(81)])
65 hsop_mock.assert_called_once_with([80, 81])
66
67 def test_idempotent(self):
68 with patch('hooks.get_open_ports', return_value=[81, 82]):
69 with patch('hooks.open_port') as hop_mock:
70 with patch('hooks.set_open_ports') as hsop_mock:
71 hooks.ensure_ports([81, 83, 82])
72 self.assertEqual(hop_mock.mock_calls, [call(83)])
73 hsop_mock.assert_called_once_with([81, 82, 83])
74
75 def test_closes_unneeded(self):
76 with patch('hooks.get_open_ports', return_value=[81, 82, 83, 84]):
77 with patch('hooks.close_port') as hcp_mock:
78 with patch('hooks.set_open_ports') as hsop_mock:
79 hooks.ensure_ports([81, 82])
80 self.assertEqual(hcp_mock.mock_calls, [call(83), call(84)])
81 hsop_mock.assert_called_once_with([81, 82])
82
83
84class TestGetSetOpenPorts(TestCase):
85
86 def test_get_open_ports(self):
87 with temp_dir() as charm_dir:
88 with patch('os.environ', {'CHARM_DIR': charm_dir}):
89 self.assertEqual(hooks.get_open_ports(), [])
90 with open(os.path.join(charm_dir, 'ports.yaml'), 'w') as pfile:
91 yaml.safe_dump([47, 67], pfile)
92 self.assertEqual(hooks.get_open_ports(), [47, 67])
93
94 def test_set_open_ports(self):
95 with temp_dir() as charm_dir:
96 with patch('os.environ', {'CHARM_DIR': charm_dir}):
97 hooks.set_open_ports([44, 23])
98 with open(os.path.join(charm_dir, 'ports.yaml')) as pfile:
99 self.assertEqual(yaml.safe_load(pfile), [44, 23])
35100
=== modified file 'hooks/tests/test_vhost_config_relation.py'
--- hooks/tests/test_vhost_config_relation.py 2014-05-21 15:32:02 +0000
+++ hooks/tests/test_vhost_config_relation.py 2015-01-07 14:04:20 +0000
@@ -208,8 +208,12 @@
208 mock_create_vhost.assert_has_calls([208 mock_create_vhost.assert_has_calls([
209 call("80", template_str=b64encode("80"),209 call("80", template_str=b64encode("80"),
210 config_data=config_data, relationship_data={}),210 config_data=config_data, relationship_data={}),
211 call().__nonzero__(),
211 call("443", template_str=b64encode("443"),212 call("443", template_str=b64encode("443"),
212 config_data=config_data, relationship_data={}),213 config_data=config_data, relationship_data={}),
214 call().__nonzero__(),
213 call("444", template_str=b64encode("444"),215 call("444", template_str=b64encode("444"),
214 config_data=config_data, relationship_data={})])216 config_data=config_data, relationship_data={}),
217 call().__nonzero__(),
218 ])
215 self.assertEqual(mock_create_vhost.call_count, 3)219 self.assertEqual(mock_create_vhost.call_count, 3)

Subscribers

People subscribed via source and target branches

to all changes: