=== modified file 'hooks/hooks.py' --- hooks/hooks.py 2013-12-11 04:14:19 +0000 +++ hooks/hooks.py 2013-12-17 16:02:40 +0000 @@ -1,5 +1,6 @@ #!/usr/bin/env python +import base64 import glob import os import re @@ -34,6 +35,7 @@ default_haproxy_config_dir = "/etc/haproxy" default_haproxy_config = "%s/haproxy.cfg" % default_haproxy_config_dir default_haproxy_service_config_dir = "/var/run/haproxy" +default_haproxy_lib_dir = "/var/lib/haproxy" service_affecting_packages = ['haproxy'] dupe_options = [ @@ -247,10 +249,14 @@ # server_ip # server_port # server_options +# errorfiles: List of dicts +# http_status: status to handle +# content: base 64 content for HAProxy to +# write to socket #------------------------------------------------------------------------------ def create_listen_stanza(service_name=None, service_ip=None, service_port=None, service_options=None, - server_entries=None): + server_entries=None, service_errorfiles=None): if service_name is None or service_ip is None or service_port is None: return None fe_options = [] @@ -284,6 +290,13 @@ service_config.append("backend %s" % (service_name,)) service_config.extend(" %s" % service_option.strip() for service_option in be_options) + if service_errorfiles is not None: + for errorfile in service_errorfiles: + path = os.path.join(default_haproxy_lib_dir, + "service_%s" % service_name, + "%s.http" % errorfile["http_status"]) + service_config.append( + " errorfile %s %s" % (errorfile["http_status"], path)) if isinstance(server_entries, (list, tuple)): for (server_name, server_ip, server_port, server_options) in server_entries: @@ -596,6 +609,17 @@ log("Service: %s" % service_key) server_entries = service_config.get('servers') + errorfiles = service_config.get('errorfiles', []) + for errorfile in errorfiles: + service_name = services_dict[service_key]['service_name'] + path = os.path.join(default_haproxy_lib_dir, + "service_%s" % service_name) + if not os.path.exists(path): + os.makedirs(path) + full_path = os.path.join(path, "%s.http" % errorfile["http_status"]) + with open(full_path, 'w') as f: + f.write(base64.b64decode(errorfile["content"])) + service_name = service_config["service_name"] if not os.path.exists(default_haproxy_service_config_dir): os.mkdir(default_haproxy_service_config_dir, 0600) @@ -606,11 +630,11 @@ service_config['service_host'], service_config['service_port'], service_config['service_options'], - server_entries)) + server_entries, errorfiles)) #------------------------------------------------------------------------------ -# load_services: Convenience function that load the service snippet +# load_services: Convenience function that loads the service snippet # configuration from the filesystem. #------------------------------------------------------------------------------ def load_services(service_name=None): === modified file 'hooks/tests/test_helpers.py' --- hooks/tests/test_helpers.py 2013-10-25 21:33:52 +0000 +++ hooks/tests/test_helpers.py 2013-12-17 16:02:40 +0000 @@ -1,3 +1,4 @@ +import base64 import os from contextlib import contextmanager @@ -352,6 +353,42 @@ self.assertEqual(expected, result) + @patch.dict(os.environ, {"JUJU_UNIT_NAME": "haproxy/2"}) + def test_creates_a_listen_stanza_with_errorfiles(self): + service_name = 'some-name' + service_ip = '10.11.12.13' + service_port = 1234 + service_options = ('foo', 'bar') + server_entries = [ + ('name-1', 'ip-1', 'port-1', ('foo1', 'bar1')), + ('name-2', 'ip-2', 'port-2', ('foo2', 'bar2')), + ] + content = ("HTTP/1.0 403 Forbidden\r\n" + "Content-Type: text/html\r\n" + "\r\n" + "") + errorfiles = [{'http_status': 403, + 'content': base64.b64encode(content)}] + + result = hooks.create_listen_stanza(service_name, service_ip, + service_port, service_options, + server_entries, errorfiles) + + expected = '\n'.join(( + 'frontend haproxy-2-1234', + ' bind 10.11.12.13:1234', + ' default_backend some-name', + '', + 'backend some-name', + ' foo', + ' bar', + ' errorfile 403 /var/lib/haproxy/service_some-name/403.http', + ' server name-1 ip-1:port-1 foo1 bar1', + ' server name-2 ip-2:port-2 foo2 bar2', + )) + + self.assertEqual(expected, result) + def test_doesnt_create_listen_stanza_if_args_not_provided(self): self.assertIsNone(hooks.create_listen_stanza()) === modified file 'hooks/tests/test_peer_hooks.py' --- hooks/tests/test_peer_hooks.py 2013-09-19 12:45:40 +0000 +++ hooks/tests/test_peer_hooks.py 2013-12-17 16:02:40 +0000 @@ -1,3 +1,4 @@ +import base64 import os import yaml @@ -194,7 +195,40 @@ hooks.write_service_config(services_dict) create_listen_stanza.assert_called_with( - 'bar', 'some-host', 'some-port', 'some-options', (1, 2)) + 'bar', 'some-host', 'some-port', 'some-options', (1, 2), []) mock_open.assert_called_with( '/var/run/haproxy/bar.service', 'w') mock_file.write.assert_called_with('some content') + + @patch('hooks.create_listen_stanza') + def test_writes_errorfiles(self, create_listen_stanza): + create_listen_stanza.return_value = 'some content' + + content = ("HTTP/1.0 403 Forbidden\r\n" + "Content-Type: text/html\r\n" + "\r\n" + "") + services_dict = { + 'foo': { + 'service_name': 'bar', + 'service_host': 'some-host', + 'service_port': 'some-port', + 'service_options': 'some-options', + 'servers': (1, 2), + 'errorfiles': [{ + 'http_status': 403, + 'content': base64.b64encode(content) + }] + }, + } + + with patch.object(os.path, "exists") as exists: + exists.return_value = True + with patch_open() as (mock_open, mock_file): + hooks.write_service_config(services_dict) + + mock_open.assert_any_call( + '/var/lib/haproxy/service_bar/403.http', 'w') + mock_file.write.assert_any_call(content) + self.assertTrue(create_listen_stanza.called) +