Merge lp:~dpb/charms/precise/apache2/vhost-config-relation into lp:charms/apache2

Proposed by David Britton
Status: Merged
Merged at revision: 55
Proposed branch: lp:~dpb/charms/precise/apache2/vhost-config-relation
Merge into: lp:charms/apache2
Diff against target: 761 lines (+614/-30)
7 files modified
README.md (+55/-0)
hooks/hooks.py (+129/-30)
hooks/tests/test_config_changed.py (+73/-0)
hooks/tests/test_create_vhost.py (+106/-0)
hooks/tests/test_hooks.py (+34/-0)
hooks/tests/test_vhost_config_relation.py (+215/-0)
metadata.yaml (+2/-0)
To merge this branch: bzr merge lp:~dpb/charms/precise/apache2/vhost-config-relation
Reviewer Review Type Date Requested Status
Charles Butler (community) Approve
Review via email: mp+220295@code.launchpad.net

Description of the change

Add a vhost-config relation to the apache2 charm.

This allows a relating charm to pass the vhosts templates over relation data instead of forcing the juju admin to specify a base64 template in the apache2 juju config. I also stuck in there 'servername' and 'ssl_cert' (if it was self signed), as web apps many times need this data for url writing and for passing on to connecting clients for authentication purposes.

Included with this change are unit tests (make test).

To post a comment you must log in.
75. By David Britton

Add test_hooks file (oops)

76. By David Britton

extra param removed

77. By David Britton

- Adding note about opening non-standard ports to README

78. By David Britton

- Add some variables for testing purposes in config-changed
- Add config-changed tests (simple, only testing changed code)
- Fix small error where empty vhosts files were created in all cases

79. By David Britton

removing lint

80. By David Britton

- fixed failing tests if apache2 was not installed

81. By David Britton

- cleanup some language in README, docstrings.

Revision history for this message
Charles Butler (lazypower) wrote :

David,

Thank you for this high quality submission! I've tested this with a quick and dirty charm created in python that base64 encodes a jinja2 template and base64 encodes it according to the readme documentation.

It would be great if there were more details with specific details on how to plug this into a charm such as here is the jinja2 vhost template for a basic HTML site being served from /var/www/static - and associated example configuration to do so.

With that being said, the tests pass and it took ~ 20 minutes to write/configure/validate the addition. It performs really well from my perspective.

Thanks again! +1

If you have any questions/comments/concerns about the review contact us in #juju on irc.freenode.net or email the mailing list <email address hidden>

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'README.md'
--- README.md 2014-01-30 09:11:53 +0000
+++ README.md 2014-05-21 18:34:21 +0000
@@ -63,6 +63,9 @@
63proxying -- obviously no variables need to be specified if no63proxying -- obviously no variables need to be specified if no
64proxying is needed.64proxying is needed.
6565
66Virtual host templates can also be specified via relation. See the
67vhost-config relation section below for more information.
68
66### Using the reverseproxy relation69### Using the reverseproxy relation
6770
68The charm will create the service variable, with the `unit_name`,71The charm will create the service variable, with the `unit_name`,
@@ -183,6 +186,58 @@
183 RewriteRule ^/(.*)$ balancer://gunicorn/$1 [P,L]186 RewriteRule ^/(.*)$ balancer://gunicorn/$1 [P,L]
184 </VirtualHost>187 </VirtualHost>
185188
189### Using the vhost-config relation
190
191The nice thing about this relation, is as long as a charm support it, deploying
192apache as a front-end for a web service should be as simple as establishing the
193relation. If you need more details for how to implement this, read on.
194
195The template files themselves can be specified via this relation. This makes
196deployment of your infrastructure simpler, since users no longer need to
197specify a vhosts config option when using apache2 (though they still can). A
198candidate charm should provide a relation on the `apache-vhost-config`
199interface. This charm should simply set the following data when relating:
200
201 relation-set vhosts="- {port: '443', template: dGVtcGxhdGU=}\n- {port: '80', template: dGVtcGxhdGU=}\n"
202
203Notice the `vhosts` definition is in yaml, the format is simple. `vhosts`
204should contain a yaml encoded data structure of a list of key value hashes, or
205dictionaries. In each dictionary, `port` should be set to the port this vhost
206should listen on, `template` should be set to the base64 encoded template file.
207You can include as many of these dictionaries as you would like. If you have
208colliding port numbers across your juju infrastructure, the results will be a
209bit unpredictable.
210
211For example, if using python for your relating charm, the code to generate a
212yaml_string for a vhost on port `80` would be similar to this:
213
214 import yaml
215 import base64
216 template = get_template()
217 vhosts = [{"port": "80", "template": base64.b64encode(template)}]
218 yaml_string = yaml.dump(vhosts)
219
220Note, that if you are opening a non-standard port (80 and 443 are opened and
221understood by the default install of apache2 in Ubuntu) you will need to
222instruct Apache to `Listen` on that port in your vhost file. Something like the
223following will work in your vhost template:
224
225 Listen 8080
226 <VirtualHost *:8080>
227 ...
228 </VirtualHost>
229
230
231#### Relation settings that apache2 provides
232
233When your charm relates it will be provided with the following:
234
235 * `servername` - The Apache2 servername. This is typically needed by web
236 applications so they know how to write URLs.
237
238 * `ssl_cert` - If you asked for a selfsigned certificate, that cert will
239 be available in this setting as a base64 encoded string.
240
186241
187## Certs, keys and chains242## Certs, keys and chains
188243
189244
=== modified file 'hooks/hooks.py'
--- hooks/hooks.py 2014-02-08 15:03:11 +0000
+++ hooks/hooks.py 2014-05-21 18:34:21 +0000
@@ -18,6 +18,8 @@
18 log,18 log,
19 config as orig_config_get,19 config as orig_config_get,
20 relations_of_type,20 relations_of_type,
21 relation_set,
22 relation_ids,
21 unit_get23 unit_get
22)24)
23from charmhelpers.contrib.charmsupport import nrpe25from charmhelpers.contrib.charmsupport import nrpe
@@ -29,6 +31,7 @@
29service_affecting_packages = ['apache2']31service_affecting_packages = ['apache2']
30default_apache22_config_dir = "/etc/apache2/conf.d"32default_apache22_config_dir = "/etc/apache2/conf.d"
31default_apache24_config_dir = "/etc/apache2/conf-available"33default_apache24_config_dir = "/etc/apache2/conf-available"
34default_apache_base_dir = "/etc/apache2"
3235
33juju_warning_header = """#36juju_warning_header = """#
34# " "37# " "
@@ -140,9 +143,9 @@
140143
141def site_filename(name, enabled=False):144def site_filename(name, enabled=False):
142 if enabled:145 if enabled:
143 sites_dir = "/etc/apache2/sites-enabled"146 sites_dir = "%s/sites-enabled" % default_apache_base_dir
144 else:147 else:
145 sites_dir = "/etc/apache2/sites-available"148 sites_dir = "%s/sites-available" % default_apache_base_dir
146149
147 if is_apache24():150 if is_apache24():
148 return "{}/{}.conf".format(sites_dir, name)151 return "{}/{}.conf".format(sites_dir, name)
@@ -177,6 +180,33 @@
177 return True180 return True
178181
179182
183def _get_key_file_location(config_data):
184 """Look at the config, generate the key file location."""
185 key_file = None
186 if config_data['ssl_keylocation']:
187 key_file = '/etc/ssl/private/%s' % \
188 (config_data['ssl_keylocation'].rpartition('/')[2])
189 return key_file
190
191
192def _get_cert_file_location(config_data):
193 """Look at the config, generate the cert file location."""
194 cert_file = None
195 if config_data['ssl_certlocation']:
196 cert_file = '/etc/ssl/certs/%s' % \
197 (config_data['ssl_certlocation'].rpartition('/')[2])
198 return cert_file
199
200
201def _get_chain_file_location(config_data):
202 """Look at the config, generate the chain file location."""
203 chain_file = None
204 if config_data['ssl_chainlocation']:
205 chain_file = '/etc/ssl/certs/%s' % \
206 (config_data['ssl_chainlocation'].rpartition('/')[2])
207 return chain_file
208
209
180###############################################################################210###############################################################################
181# Hook functions211# Hook functions
182###############################################################################212###############################################################################
@@ -402,6 +432,41 @@
402 logrotate_conf.write(str(template))432 logrotate_conf.write(str(template))
403433
404434
435def create_vhost(port, protocol=None, config_key=None, template_str=None,
436 config_data={}, relationship_data={}):
437 """
438 Create and enable a vhost in apache.
439
440 @param port: port on which to listen (int)
441 @param protocol: used to name the vhost file intelligently. If not
442 specified the port will be used instead. (ex: http, https)
443 @param config_key: key in the configuration to look up to
444 retrieve the template.
445 @param template_str: The template itself as a string.
446 @param config_data: juju get-config configuration data.
447 @param relationship_data: if in a relationship, pass in the appropriate
448 structure. This will be used to inform the template.
449 """
450 if protocol is None:
451 protocol = str(port)
452 close_port(port)
453 if template_str is None:
454 if not config_key or not config_data[config_key]:
455 log("Vhost Template not provided, not configuring: %s" % port)
456 return
457 template_str = config_data[config_key]
458 from jinja2 import Template
459 template = Template(str(base64.b64decode(template_str)))
460 template_data = dict(config_data.items() + relationship_data.items())
461 vhost_name = '%s_%s' % (config_data['servername'], protocol)
462 vhost_file = site_filename(vhost_name)
463 log("Writing file %s with config and relation data" % vhost_file)
464 with open(vhost_file, 'w') as vhost:
465 vhost.write(str(template.render(template_data)))
466 open_port(port)
467 subprocess.call(['/usr/sbin/a2ensite', vhost_name])
468
469
405def config_changed():470def config_changed():
406 relationship_data = {}471 relationship_data = {}
407 config_data = config_get()472 config_data = config_get()
@@ -435,35 +500,19 @@
435 apt_get_purge('apache2-mpm-worker')500 apt_get_purge('apache2-mpm-worker')
436 create_mpm_workerfile()501 create_mpm_workerfile()
437 create_security()502 create_security()
503
438 ports = {'http': 80, 'https': 443}504 ports = {'http': 80, 'https': 443}
439 for proto in ports.keys():505 for protocol, port in ports.iteritems():
440 template_var = 'vhost_%s_template' % (proto)506 create_vhost(
441 template_data = dict(config_data.items() + relationship_data.items())507 port,
442 close_port(ports[proto])508 protocol=protocol,
443 if template_var in config_data:509 config_key="vhost_%s_template" % protocol,
444 vhost_name = '%s_%s' % (config_data['servername'], proto)510 config_data=config_data,
445 vhost_file = site_filename(vhost_name)511 relationship_data=relationship_data)
446 from jinja2 import Template
447 template = Template(
448 str(base64.b64decode(config_data[template_var])))
449 log("Writing file %s with config and relation data" % vhost_file)
450 with open(vhost_file, 'w') as vhost:
451 vhost.write(str(template.render(template_data)))
452 open_port(ports[proto])
453 subprocess.call(['/usr/sbin/a2ensite', vhost_name])
454512
455 cert_file = None513 cert_file = _get_cert_file_location(config_data)
456 if config_data['ssl_certlocation']:514 key_file = _get_key_file_location(config_data)
457 cert_file = '/etc/ssl/certs/%s' % \515 chain_file = _get_chain_file_location(config_data)
458 (config_data['ssl_certlocation'].rpartition('/')[2])
459 key_file = None
460 if config_data['ssl_keylocation']:
461 key_file = '/etc/ssl/private/%s' % \
462 (config_data['ssl_keylocation'].rpartition('/')[2])
463 chain_file = None
464 if config_data['ssl_chainlocation']:
465 chain_file = '/etc/ssl/certs/%s' % \
466 (config_data['ssl_chainlocation'].rpartition('/')[2])
467516
468 if cert_file is not None and key_file is not None:517 if cert_file is not None and key_file is not None:
469 # ssl_cert is SELFSIGNED so generate self-signed certificate for use.518 # ssl_cert is SELFSIGNED so generate self-signed certificate for use.
@@ -567,14 +616,62 @@
567 if not os.path.exists('/etc/apache2/security'):616 if not os.path.exists('/etc/apache2/security'):
568 os.mkdir('/etc/apache2/security', 0755)617 os.mkdir('/etc/apache2/security', 0755)
569 with open('/etc/apache2/security/allowed-ops.txt', 'w') as f:618 with open('/etc/apache2/security/allowed-ops.txt', 'w') as f:
570 f.write(config_data['openid_provider'].replace(',','\n'))619 f.write(config_data['openid_provider'].replace(',', '\n'))
571 f.write('\n')620 f.write('\n')
572 os.chmod(key_file, 0444)621 os.chmod(key_file, 0444)
573622
623 update_vhost_config_relation()
574 update_nrpe_checks()624 update_nrpe_checks()
575 ship_logrotate_conf()625 ship_logrotate_conf()
576626
577627
628def update_vhost_config_relation():
629 """
630 Update the vhost file and include the certificate in the relation
631 if it is self-signed.
632 """
633 relation_data = relations_of_type("vhost-config")
634 config_data = config_get()
635 if relation_data is None:
636 return
637
638 for unit_data in relation_data:
639 if "vhosts" in unit_data:
640 all_relation_data = {}
641 all_relation_data.update(
642 get_reverseproxy_data(relation='reverseproxy'))
643 all_relation_data.update(
644 get_reverseproxy_data(relation='website-cache'))
645 try:
646 vhosts = yaml.safe_load(unit_data["vhosts"])
647 for vhost in vhosts:
648 create_vhost(
649 vhost["port"],
650 template_str=vhost["template"],
651 config_data=config_data,
652 relationship_data=all_relation_data)
653 except Exception as e:
654 log("Error reading configuration data from relation! %s" % e)
655 raise
656
657 if service_apache2("check"):
658 service_apache2("reload")
659
660 vhost_relation_settings = {
661 "servername": config_data["servername"]}
662
663 cert_file = _get_cert_file_location(config_data)
664 key_file = _get_key_file_location(config_data)
665
666 if cert_file is not None and key_file is not None:
667 if config_data['ssl_cert'] and config_data['ssl_cert'] == "SELFSIGNED":
668 with open(cert_file, 'r') as f:
669 cert = base64.b64encode(f.read())
670 vhost_relation_settings["ssl_cert"] = cert
671 for id in relation_ids("vhost-config"):
672 relation_set(relation_id=id, relation_settings=vhost_relation_settings)
673
674
578def start_hook():675def start_hook():
579 if service_apache2("status"):676 if service_apache2("status"):
580 return(service_apache2("restart"))677 return(service_apache2("restart"))
@@ -640,6 +737,8 @@
640 elif hook_name in ("nrpe-external-master-relation-changed",737 elif hook_name in ("nrpe-external-master-relation-changed",
641 "local-monitors-relation-changed"):738 "local-monitors-relation-changed"):
642 update_nrpe_checks()739 update_nrpe_checks()
740 elif hook_name == "vhost-config-relation-changed":
741 config_changed()
643 else:742 else:
644 print "Unknown hook"743 print "Unknown hook"
645 sys.exit(1)744 sys.exit(1)
646745
=== added file 'hooks/tests/test_config_changed.py'
--- hooks/tests/test_config_changed.py 1970-01-01 00:00:00 +0000
+++ hooks/tests/test_config_changed.py 2014-05-21 18:34:21 +0000
@@ -0,0 +1,73 @@
1from testtools import TestCase
2import hooks
3from mock import patch
4import shutil
5import tempfile
6import os
7
8
9class ConfigChangedTest(TestCase):
10 def setUp(self):
11 super(ConfigChangedTest, self).setUp()
12 self.dirname = tempfile.mkdtemp()
13
14 def tearDown(self):
15 super(ConfigChangedTest, self).tearDown()
16 if os.path.exists(self.dirname):
17 shutil.rmtree(self.dirname)
18
19 @patch('hooks.subprocess.call')
20 @patch('hooks.close_port')
21 @patch('hooks.open_port')
22 @patch('hooks.conf_disable')
23 @patch('hooks.ensure_extra_packages')
24 @patch('hooks.ensure_package_status')
25 @patch('hooks.ship_logrotate_conf')
26 @patch('hooks.update_nrpe_checks')
27 @patch('hooks.run')
28 @patch('hooks.create_security')
29 @patch('hooks.create_mpm_workerfile')
30 @patch('hooks.log')
31 @patch('hooks.relation_ids')
32 @patch('hooks.relations_of_type')
33 @patch('hooks.config_get')
34 @patch('hooks.get_reverseproxy_data')
35 @patch('hooks.service_apache2')
36 @patch('hooks.relation_set')
37 def test_config_changed_ensure_empty_site_dir(
38 self, mock_relation_set, mock_service_apache2,
39 mock_reverseproxy, mock_config_get,
40 mock_relations_of_type, mock_relation_ids, mock_log,
41 mock_create_mpm_workerfile, mock_create_security, mock_run,
42 mock_update_nrpe_checks, mock_ship_logrotate_conf,
43 mock_ensure_package_status, mock_ensure_extra_packages,
44 mock_conf_disable, mock_open_port, mock_close_port, mock_call):
45 """config-changed hook: Site directories should be empty."""
46 mock_config_get.return_value = {
47 "ssl_cert": "",
48 "ssl_key": "",
49 "package_status": "",
50 "enable_modules": "",
51 "disable_modules": "",
52 "mpm_type": "",
53 "ssl_certlocation": "",
54 "ssl_keylocation": "",
55 "ssl_chainlocation": "",
56 "use_rsyslog": "",
57 "config_change_command": "",
58 "openid_provider": "",
59 "servername": "foobar",
60 "vhost_http_template": "",
61 "vhost_https_template": "",
62 }
63 hooks.default_apache_base_dir = self.dirname
64 hooks.default_apache22_config_dir = "%s/conf.d" % self.dirname
65 hooks.default_apache24_config_dir = "%s/conf-available" % self.dirname
66 os.mkdir("%s/sites-enabled" % self.dirname)
67 os.mkdir("%s/sites-available" % self.dirname)
68 os.mkdir("%s/conf.d" % self.dirname)
69 hooks.config_changed()
70 self.assertEqual(
71 len(os.listdir("%s/%s" % (self.dirname, "sites-enabled"))), 0)
72 self.assertEqual(
73 len(os.listdir("%s/%s" % (self.dirname, "sites-available"))), 0)
074
=== added file 'hooks/tests/test_create_vhost.py'
--- hooks/tests/test_create_vhost.py 1970-01-01 00:00:00 +0000
+++ hooks/tests/test_create_vhost.py 2014-05-21 18:34:21 +0000
@@ -0,0 +1,106 @@
1from testtools import TestCase
2import hooks
3import base64
4import tempfile
5from mock import patch
6
7
8class CreateVhostTest(TestCase):
9 def setUp(self):
10 super(CreateVhostTest, self).setUp()
11
12 @patch('hooks.log')
13 @patch('hooks.close_port')
14 def test_create_vhost_no_template(self, mock_close_port, mock_log):
15 """User did not specify a template, error logged."""
16 hooks.create_vhost("80")
17 mock_log.assert_called_once_with(
18 'Vhost Template not provided, not configuring: 80')
19
20 @patch('hooks.close_port')
21 @patch('hooks.site_filename')
22 @patch('hooks.open_port')
23 @patch('hooks.subprocess.call')
24 def test_create_vhost_template_name_port(
25 self, mock_call, mock_open_port, mock_site_filename,
26 mock_close_port):
27 """Check that name generated is sane as a port."""
28 config = {"servername": "unused"}
29 file = tempfile.NamedTemporaryFile()
30 filename = file.name
31 mock_site_filename.return_value = filename
32 hooks.create_vhost(
33 "80",
34 config_data=config,
35 template_str=base64.b64encode("foo"))
36 mock_site_filename.assert_called_once_with("unused_80")
37
38 @patch('hooks.close_port')
39 @patch('hooks.site_filename')
40 @patch('hooks.open_port')
41 @patch('hooks.subprocess.call')
42 def test_create_vhost_template_name_protocol(
43 self, mock_call, mock_open_port, mock_site_filename,
44 mock_close_port):
45 """Check that name generated is sane as a protocol."""
46 config = {"servername": "unused"}
47 file = tempfile.NamedTemporaryFile()
48 filename = file.name
49 mock_site_filename.return_value = filename
50 hooks.create_vhost(
51 "80",
52 protocol="httpfoo",
53 config_data=config,
54 template_str=base64.b64encode("foo"))
55 mock_site_filename.assert_called_once_with("unused_httpfoo")
56
57 @patch('hooks.close_port')
58 @patch('hooks.site_filename')
59 @patch('hooks.open_port')
60 @patch('hooks.subprocess.call')
61 def test_create_vhost_template(
62 self, mock_call, mock_open_port, mock_site_filename,
63 mock_close_port):
64 """
65 Template passed in as string.
66
67 Verify relationship and config inform template as well.
68 """
69 template = ("{{servername}} {{ foo }}")
70 config = {"servername": "test_only"}
71 relationship = {"foo": "bar"}
72 file = tempfile.NamedTemporaryFile()
73 filename = file.name
74 mock_site_filename.return_value = filename
75 hooks.create_vhost(
76 "80",
77 config_data=config,
78 relationship_data=relationship,
79 template_str=base64.b64encode(template))
80 with open(filename, 'r') as f:
81 contents = f.read()
82 self.assertEqual(contents, "test_only bar")
83
84 @patch('hooks.close_port')
85 @patch('hooks.site_filename')
86 @patch('hooks.open_port')
87 @patch('hooks.subprocess.call')
88 def test_create_vhost_template_config(
89 self, mock_call, mock_open_port, mock_site_filename,
90 mock_close_port):
91 """Template passed in as config setting."""
92 template = ("one\n"
93 "two\n"
94 "three")
95 config = {"servername": "unused",
96 "vhost_template": base64.b64encode(template)}
97 file = tempfile.NamedTemporaryFile()
98 filename = file.name
99 mock_site_filename.return_value = filename
100 hooks.create_vhost(
101 "80",
102 config_key="vhost_template",
103 config_data=config)
104 with open(filename, 'r') as f:
105 contents = f.read()
106 self.assertEqual(contents, template)
0107
=== added file 'hooks/tests/test_hooks.py'
--- hooks/tests/test_hooks.py 1970-01-01 00:00:00 +0000
+++ hooks/tests/test_hooks.py 2014-05-21 18:34:21 +0000
@@ -0,0 +1,34 @@
1from testtools import TestCase
2import hooks
3
4
5class HooksTest(TestCase):
6 def test__get_key_file_location_empty(self):
7 """No ssl_keylocation, expect None."""
8 self.assertEqual(hooks._get_key_file_location(
9 {"ssl_keylocation": None}), None)
10
11 def test__get_key_file_location(self):
12 """ssl_keylocation, expect correct path."""
13 self.assertEqual(hooks._get_key_file_location(
14 {"ssl_keylocation": "foo"}), "/etc/ssl/private/foo")
15
16 def test__get_cert_file_location_empty(self):
17 """No ssl_keylocation, expect None."""
18 self.assertEqual(hooks._get_cert_file_location(
19 {"ssl_certlocation": None}), None)
20
21 def test__get_cert_file_location(self):
22 """ssl_keylocation, expect correct path."""
23 self.assertEqual(hooks._get_cert_file_location(
24 {"ssl_certlocation": "foo"}), "/etc/ssl/certs/foo")
25
26 def test__get_chain_file_location_empty(self):
27 """No ssl_keylocation, expect None."""
28 self.assertEqual(hooks._get_chain_file_location(
29 {"ssl_chainlocation": None}), None)
30
31 def test__get_chain_file_location(self):
32 """ssl_keylocation, expect correct path."""
33 self.assertEqual(hooks._get_chain_file_location(
34 {"ssl_chainlocation": "foo"}), "/etc/ssl/certs/foo")
035
=== added file 'hooks/tests/test_vhost_config_relation.py'
--- hooks/tests/test_vhost_config_relation.py 1970-01-01 00:00:00 +0000
+++ hooks/tests/test_vhost_config_relation.py 2014-05-21 18:34:21 +0000
@@ -0,0 +1,215 @@
1from testtools import TestCase
2import hooks
3from base64 import b64encode
4from mock import patch, call
5import yaml
6import tempfile
7import os
8import shutil
9
10
11class CreateVhostTest(TestCase):
12
13 def setUp(self):
14 super(CreateVhostTest, self).setUp()
15 self.dirname = tempfile.mkdtemp()
16 os.mkdir("%s/sites-enabled" % self.dirname)
17 os.mkdir("%s/sites-available" % self.dirname)
18 os.mkdir("%s/conf.d" % self.dirname)
19 hooks.default_apache_base_dir = self.dirname
20 hooks.default_apache22_config_dir = "%s/conf.d" % self.dirname
21 hooks.default_apache24_config_dir = "%s/conf-available" % self.dirname
22
23 def tearDown(self):
24 super(CreateVhostTest, self).tearDown()
25 if os.path.exists(self.dirname):
26 shutil.rmtree(self.dirname)
27
28 @patch('hooks.log')
29 @patch('subprocess.call')
30 @patch('hooks.close_port')
31 def test_create_vhost_missing_template(
32 self, mock_close_port, mock_call, mock_log):
33 """Create a vhost file, check contents."""
34 hooks.create_vhost(80)
35 mock_log.assert_called_once_with(
36 "Vhost Template not provided, not configuring: %s" % 80)
37 mock_close_port.assert_called_once()
38 self.assertEqual(
39 len(os.listdir("%s/%s" % (self.dirname, "sites-available"))), 0)
40
41 @patch('hooks.log')
42 @patch('subprocess.call')
43 @patch('hooks.close_port')
44 @patch('hooks.open_port')
45 def test_create_vhost_template_through_config_no_protocol(
46 self, mock_open_port, mock_close_port, mock_call, mock_log):
47 """Create a vhost file, check contents."""
48 template = b64encode("http://{{ variable }}/")
49 config_data = {
50 "template": template,
51 "servername": "test_only"}
52 relationship_data = {
53 "variable": "fantastic"}
54 hooks.create_vhost(
55 80, config_data=config_data, config_key="template",
56 relationship_data=relationship_data)
57 filename = hooks.site_filename("test_only_80")
58 self.assertTrue(os.path.exists(filename))
59 with open(filename, 'r') as file:
60 contents = file.read()
61 self.assertEqual(contents, 'http://fantastic/')
62 mock_open_port.assert_called_once()
63 mock_close_port.assert_called_once()
64 self.assertEqual(
65 len(os.listdir("%s/%s" % (self.dirname, "sites-available"))), 1)
66
67 @patch('hooks.log')
68 @patch('subprocess.call')
69 @patch('hooks.close_port')
70 @patch('hooks.open_port')
71 def test_create_vhost_template_through_config_with_protocol(
72 self, mock_open_port, mock_close_port, mock_call, mock_log):
73 """Create a vhost file, check contents."""
74 template = b64encode("http://{{ variable }}/")
75 config_data = {
76 "template": template,
77 "servername": "test_only"}
78 relationship_data = {
79 "variable": "fantastic"}
80 hooks.create_vhost(
81 80, config_data=config_data, config_key="template",
82 protocol='http', relationship_data=relationship_data)
83 filename = hooks.site_filename("test_only_http")
84 self.assertTrue(os.path.exists(filename))
85 with open(filename, 'r') as file:
86 contents = file.read()
87 self.assertEqual(contents, 'http://fantastic/')
88 mock_open_port.assert_called_once()
89 mock_close_port.assert_called_once()
90 self.assertEqual(
91 len(os.listdir("%s/%s" % (self.dirname, "sites-available"))), 1)
92
93 @patch('hooks.log')
94 @patch('subprocess.call')
95 @patch('hooks.close_port')
96 @patch('hooks.open_port')
97 def test_create_vhost_template_directly(
98 self, mock_open_port, mock_close_port,
99 mock_call, mock_log):
100 """Create a vhost file, check contents."""
101 template = b64encode("http://{{ variable }}/")
102 config_data = {
103 "servername": "test_only"}
104 relationship_data = {
105 "variable": "fantastic"}
106 hooks.create_vhost(
107 80, template_str=template, config_data=config_data,
108 config_key="template", relationship_data=relationship_data)
109 filename = hooks.site_filename("test_only_80")
110 self.assertTrue(os.path.exists(filename))
111 with open(filename, 'r') as file:
112 contents = file.read()
113 self.assertEqual(contents, 'http://fantastic/')
114 mock_open_port.assert_called_once()
115 mock_close_port.assert_called_once()
116 self.assertEqual(
117 len(os.listdir("%s/%s" % (self.dirname, "sites-available"))), 1)
118
119
120class VhostConfigRelationTest(TestCase):
121 @patch('hooks.service_apache2')
122 @patch('hooks.relation_ids')
123 @patch('hooks.relations_of_type')
124 @patch('hooks.get_reverseproxy_data')
125 @patch('hooks.config_get')
126 def test_vhost_config_relation_changed_no_relation_data(
127 self, mock_config_get, mock_relation_get,
128 mock_relations_of_type, mock_relation_ids,
129 mock_service_apache2):
130 """No relation data, do nothing."""
131 mock_relation_get.return_value = None
132 hooks.update_vhost_config_relation()
133
134 @patch('hooks.relations_of_type')
135 @patch('hooks.service_apache2')
136 @patch('hooks.config_get')
137 @patch('hooks.get_reverseproxy_data')
138 @patch('hooks.log')
139 def test_vhost_config_relation_changed_vhost_ports_only(
140 self, mock_log, mock_reverseproxy, mock_config_get,
141 mock_service_apache2, mock_relations_of_type):
142 """vhost_ports only specified, hook should exit with error"""
143 mock_relations_of_type.return_value = [
144 {'vhosts': yaml.dump([{'port': "5555"}])}]
145 mock_config_get.return_value = {}
146 self.assertRaisesRegexp(
147 KeyError, "template", hooks.update_vhost_config_relation)
148
149 @patch('hooks.log')
150 @patch('hooks.relation_ids')
151 @patch('hooks.relations_of_type')
152 @patch('hooks.config_get')
153 @patch('hooks.get_reverseproxy_data')
154 @patch('hooks.create_vhost')
155 @patch('hooks.service_apache2')
156 @patch('hooks.relation_set')
157 def test_vhost_config_relation_changed_vhost_ports_single(
158 self, mock_relation_set, mock_service_apache2,
159 mock_create_vhost, mock_reverseproxy, mock_config_get,
160 mock_relations_of_type, mock_relation_ids, mock_log):
161 """A single vhost entry is created."""
162 mock_relation_ids.return_value = ["testonly"]
163 rel = {"vhosts": yaml.dump([{
164 'port': '80',
165 'template': b64encode("foo")
166 }])}
167 config_data = {
168 "servername": "unused",
169 "ssl_certlocation": "unused",
170 "ssl_keylocation": "unused",
171 "ssl_cert": ""}
172 mock_config_get.return_value = config_data
173 mock_relations_of_type.return_value = [rel]
174 hooks.update_vhost_config_relation()
175 mock_create_vhost.assert_called_once_with(
176 "80",
177 template_str=b64encode("foo"),
178 config_data=config_data,
179 relationship_data={}
180 )
181
182 @patch('hooks.log')
183 @patch('hooks.relation_ids')
184 @patch('hooks.relations_of_type')
185 @patch('hooks.config_get')
186 @patch('hooks.get_reverseproxy_data')
187 @patch('hooks.create_vhost')
188 @patch('hooks.service_apache2')
189 @patch('hooks.relation_set')
190 def test_vhost_config_relation_changed_vhost_ports_multi(
191 self, mock_relation_set, mock_service_apache2,
192 mock_create_vhost, mock_reverseproxy, mock_config_get,
193 mock_relations_of_type, mock_relation_ids, mock_log):
194 """Multiple vhost entries are created."""
195 mock_relation_ids.return_value = ["testonly"]
196 rel = {"vhosts": yaml.dump([
197 {'port': "80", 'template': b64encode("80")},
198 {'port': "443", 'template': b64encode("443")},
199 {'port': "444", 'template': b64encode("444")}])}
200 mock_relations_of_type.return_value = [rel]
201 config_data = {
202 "servername": "unused",
203 "ssl_certlocation": "unused",
204 "ssl_keylocation": "unused",
205 "ssl_cert": ""}
206 mock_config_get.return_value = config_data
207 hooks.update_vhost_config_relation()
208 mock_create_vhost.assert_has_calls([
209 call("80", template_str=b64encode("80"),
210 config_data=config_data, relationship_data={}),
211 call("443", template_str=b64encode("443"),
212 config_data=config_data, relationship_data={}),
213 call("444", template_str=b64encode("444"),
214 config_data=config_data, relationship_data={})])
215 self.assertEqual(mock_create_vhost.call_count, 3)
0216
=== added symlink 'hooks/vhost-config-relation-changed'
=== target is u'hooks.py'
=== modified file 'metadata.yaml'
--- metadata.yaml 2013-05-28 20:19:08 +0000
+++ metadata.yaml 2014-05-21 18:34:21 +0000
@@ -27,3 +27,5 @@
27 interface: http27 interface: http
28 logging:28 logging:
29 interface: syslog29 interface: syslog
30 vhost-config:
31 interface: apache-vhost-config

Subscribers

People subscribed via source and target branches

to all changes: