Merge lp:~mthaddon/charms/precise/haproxy/mini-sprint-sf into lp:charms/haproxy

Proposed by Tom Haddon on 2012-07-13
Status: Merged
Approved by: Clint Byrum on 2012-07-23
Approved revision: 54
Merge reported by: Juan L. Negron
Merged at revision: not available
Proposed branch: lp:~mthaddon/charms/precise/haproxy/mini-sprint-sf
Merge into: lp:charms/haproxy
Diff against target: 889 lines (+683/-141)
10 files modified
config.yaml (+108/-0)
hooks/hooks.py (+573/-0)
hooks/install (+0/-11)
hooks/reverseproxy-relation-changed (+0/-114)
hooks/start (+0/-1)
hooks/stop (+0/-3)
hooks/website-relation-changed (+0/-8)
hooks/website-relation-joined (+0/-2)
metadata.yaml (+1/-1)
revision (+1/-1)
To merge this branch: bzr merge lp:~mthaddon/charms/precise/haproxy/mini-sprint-sf
Reviewer Review Type Date Requested Status
Clint Byrum (community) 2012-07-13 Approve on 2012-07-23
Review via email: mp+114951@code.launchpad.net

Description of the Change

This is the outcome of a mini-sprint with myself and Juan Negron. We've added the ability to customise the haproxy config file's global and default sections as needed, allowed for an optional monitoring interface that's restricted to localhost by default, and also created the ability to specify multiple listen stanzas each of which can be customised as needed.

To post a comment you must log in.
Clint Byrum (clint-fewbar) wrote :

Alright, the discussions on IRC were very helpful and I wanted to record the outcome here.

* First, this includes what is basically a workaround for not having "relation configs". Putting matching pairs of configuration data in the config options of each charm does not feel natural and will lead to confusion. A bug has been filed in juju-core to address this:

https://bugs.launchpad.net/juju-core/+bug/1026422

* Second, the abstractions in here are really beautiful. I think that many of them belong in charm-helper so that other python charms can make use of them and simplify their code.

* Third, nicely done, please merge!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'config.yaml'
2--- config.yaml 1970-01-01 00:00:00 +0000
3+++ config.yaml 2012-07-13 21:44:18 +0000
4@@ -0,0 +1,108 @@
5+options:
6+ global_log:
7+ default: "127.0.0.1 local0, 127.0.0.1 local1 notice"
8+ type: string
9+ description: Global log line ( multiples ... comma separated list )
10+ global_maxconn:
11+ default: 4096
12+ type: int
13+ description: |
14+ Sets the maximum per-process number of concurrent connections to
15+ <number>.
16+ global_user:
17+ default: "haproxy"
18+ type: string
19+ description: User
20+ global_group:
21+ default: "haproxy"
22+ type: string
23+ description: Group
24+ global_debug:
25+ default: False
26+ type: boolean
27+ description: Debug or not
28+ global_quiet:
29+ default: False
30+ type: boolean
31+ description: Quiet
32+ global_spread_checks:
33+ default: 0
34+ type: int
35+ descriptions: |
36+ Sometimes it is desirable to avoid sending health checks to servers at
37+ exact intervals, for instance when many logical servers are located on
38+ the same physical server. With the help of this parameter, it becomes
39+ possible to add some randomness in the check interval between 0 and
40+ +/- 50%. A value between 2 and 5 seems to show good results.
41+ default_log:
42+ default: "global"
43+ type: string
44+ description: Default log
45+ default_mode:
46+ default: "http"
47+ type: string
48+ description: Default mode
49+ default_options:
50+ default: "httplog, dontlognull"
51+ type: string
52+ description: Default options
53+ default_retries:
54+ default: 3
55+ type: int
56+ description: |
57+ Set the number of retries to perform on a server after a connection
58+ failure. It is important to understand that this value applies to the
59+ number of connection attempts, not full requests. When a connection
60+ has effectively been established to a server, there will be no more
61+ retry.
62+ In order to avoid immediate reconnections to a server which is
63+ restarting, a turn-around timer of 1 second is applied before a retry
64+ occurs.
65+ default_timeouts:
66+ default: "queue 1000, connect 1000, client 1000, server 1000"
67+ type: string
68+ description: Default timeouts
69+ enable_monitoring:
70+ default: False
71+ type: boolean
72+ description: Enable monitoring
73+ monitoring_port:
74+ default: 10000
75+ type: int
76+ description: Default monitoring port
77+ monitoring_allowed_cidr:
78+ default: "127.0.0.1/32"
79+ type: string
80+ description: |
81+ CIDR allowed ( multiple CIDRs separated by space ) access to the
82+ monitoring interface.
83+ monitoring_username:
84+ default: "haproxy"
85+ type: string
86+ description: Monitoring username
87+ monitoring_password:
88+ default: "changeme"
89+ type: string
90+ description: |
91+ Password to the monitoring interface ( if "changeme", a new password
92+ will be generated and displayed in juju-log )
93+ monitoring_stats_refresh:
94+ default: 3
95+ type: int
96+ description: Monitoring interface refresh interval (in seconds)
97+ services:
98+ default: |
99+ - service_name: haproxy_service
100+ service_host: "0.0.0.0"
101+ service_port: 80
102+ service_options: [balance leastconn]
103+ server_options: maxconn 100
104+ type: string
105+ description: |
106+ Services definition(s). Although the variable type is a string, this is
107+ interpreted in the charm as yaml. To use multiple services within the
108+ same haproxy instance, specify all of the variables (service_name,
109+ service_host, service_port, service_options, server_options) with a "-"
110+ before the first variable, service_name, as above. Service options is a
111+ comma separated list, server options will be appended as a string to
112+ the individual server lines for a given listen stanza.
113
114=== added symlink 'hooks/config-changed'
115=== target is u'./hooks.py'
116=== added file 'hooks/hooks.py'
117--- hooks/hooks.py 1970-01-01 00:00:00 +0000
118+++ hooks/hooks.py 2012-07-13 21:44:18 +0000
119@@ -0,0 +1,573 @@
120+#!/usr/bin/env python
121+
122+import json
123+import glob
124+import os
125+import random
126+import re
127+import socket
128+import string
129+import subprocess
130+import sys
131+import yaml
132+
133+
134+###############################################################################
135+# Global variables
136+###############################################################################
137+default_haproxy_config_dir = "/etc/haproxy"
138+default_haproxy_config = "%s/haproxy.cfg" % default_haproxy_config_dir
139+default_haproxy_service_config_dir = "/var/run/haproxy"
140+hook_name = os.path.basename(sys.argv[0])
141+
142+###############################################################################
143+# Supporting functions
144+###############################################################################
145+
146+
147+#------------------------------------------------------------------------------
148+# config_get: Returns a dictionary containing all of the config information
149+# Optional parameter: scope
150+# scope: limits the scope of the returned configuration to the
151+# desired config item.
152+#------------------------------------------------------------------------------
153+def config_get(scope=None):
154+ try:
155+ config_cmd_line = ['config-get']
156+ if scope is not None:
157+ config_cmd_line.append(scope)
158+ config_cmd_line.append('--format=json')
159+ config_data = json.loads(subprocess.check_output(config_cmd_line))
160+ except:
161+ config_data = None
162+ finally:
163+ return(config_data)
164+
165+
166+#------------------------------------------------------------------------------
167+# relation_get: Returns a dictionary containing the relation information
168+# Optional parameters: scope, relation_id
169+# scope: limits the scope of the returned data to the
170+# desired item.
171+# unit_name: limits the data ( and optionally the scope )
172+# to the specified unit
173+#------------------------------------------------------------------------------
174+def relation_get(scope=None, unit_name=None):
175+ try:
176+ relation_cmd_line = ['relation-get', '--format=json']
177+ if scope is not None:
178+ relation_cmd_line.append(scope)
179+ else:
180+ relation_cmd_line.append('')
181+ if unit_name is not None:
182+ relation_cmd_line.append(unit_name)
183+ relation_data = json.loads(subprocess.check_output(relation_cmd_line))
184+ except:
185+ relation_data = None
186+ finally:
187+ return(relation_data)
188+
189+
190+#------------------------------------------------------------------------------
191+# apt_get_install( package ): Installs a package
192+#------------------------------------------------------------------------------
193+def apt_get_install(packages=None):
194+ if packages is None:
195+ return(False)
196+ cmd_line = ['apt-get', '-y', 'install', '-qq']
197+ cmd_line.append(packages)
198+ return(subprocess.call(cmd_line))
199+
200+
201+#------------------------------------------------------------------------------
202+# enable_haproxy: Enabled haproxy at boot time
203+#------------------------------------------------------------------------------
204+def enable_haproxy():
205+ default_haproxy = "/etc/default/haproxy"
206+ enabled_haproxy = \
207+ open(default_haproxy).read().replace('ENABLED=0', 'ENABLED=1')
208+ with open(default_haproxy, 'w') as f:
209+ f.write(enabled_haproxy)
210+
211+
212+#------------------------------------------------------------------------------
213+# create_haproxy_globals: Creates the global section of the haproxy config
214+#------------------------------------------------------------------------------
215+def create_haproxy_globals():
216+ config_data = config_get()
217+ global_log = config_data['global_log'].split(',')
218+ haproxy_globals = []
219+ haproxy_globals.append('global')
220+ for global_log_item in global_log:
221+ haproxy_globals.append(" log %s" % global_log_item.strip())
222+ haproxy_globals.append(" maxconn %d" % config_data['global_maxconn'])
223+ haproxy_globals.append(" user %s" % config_data['global_user'])
224+ haproxy_globals.append(" group %s" % config_data['global_group'])
225+ if config_data['global_debug'] is True:
226+ haproxy_globals.append(" debug")
227+ if config_data['global_quiet'] is True:
228+ haproxy_globals.append(" quiet")
229+ haproxy_globals.append(" spread-checks %d" % \
230+ config_data['global_spread_checks'])
231+ return('\n'.join(haproxy_globals))
232+
233+
234+#------------------------------------------------------------------------------
235+# create_haproxy_defaults: Creates the defaults section of the haproxy config
236+#------------------------------------------------------------------------------
237+def create_haproxy_defaults():
238+ config_data = config_get()
239+ default_options = config_data['default_options'].split(',')
240+ default_timeouts = config_data['default_timeouts'].split(',')
241+ haproxy_defaults = []
242+ haproxy_defaults.append("defaults")
243+ haproxy_defaults.append(" log %s" % config_data['default_log'])
244+ haproxy_defaults.append(" mode %s" % config_data['default_mode'])
245+ for option_item in default_options:
246+ haproxy_defaults.append(" option %s" % option_item.strip())
247+ haproxy_defaults.append(" retries %d" % config_data['default_retries'])
248+ for timeout_item in default_timeouts:
249+ haproxy_defaults.append(" timeout %s" % timeout_item.strip())
250+ return('\n'.join(haproxy_defaults))
251+
252+
253+#------------------------------------------------------------------------------
254+# load_haproxy_config: Convenience function that loads (as a string) the
255+# current haproxy configuration file.
256+# Returns a string containing the haproxy config or
257+# None
258+#------------------------------------------------------------------------------
259+def load_haproxy_config(haproxy_config_file="/etc/haproxy/haproxy.cfg"):
260+ if os.path.isfile(haproxy_config_file):
261+ return(open(haproxy_config_file).read())
262+ else:
263+ return(None)
264+
265+
266+#------------------------------------------------------------------------------
267+# get_monitoring_password: Gets the monitoring password from the
268+# haproxy config.
269+# This prevents the password from being constantly
270+# regenerated by the system.
271+#------------------------------------------------------------------------------
272+def get_monitoring_password(haproxy_config_file="/etc/haproxy/haproxy.cfg"):
273+ haproxy_config = load_haproxy_config(haproxy_config_file)
274+ if haproxy_config is None:
275+ return(None)
276+ m = re.search("stats auth\s+(\w+):(\w+)", haproxy_config)
277+ if m is not None:
278+ return(m.group(2))
279+ else:
280+ return(None)
281+
282+
283+#------------------------------------------------------------------------------
284+# get_service_ports: Convenience function that scans the existing haproxy
285+# configuration file and returns a list of the existing
286+# ports being used. This is necessary to know which ports
287+# to open and close when exposing/unexposing a service
288+#------------------------------------------------------------------------------
289+def get_service_ports(haproxy_config_file="/etc/haproxy/haproxy.cfg"):
290+ haproxy_config = load_haproxy_config(haproxy_config_file)
291+ if haproxy_config is None:
292+ return(None)
293+ return(re.findall("listen.*:(.*)", haproxy_config))
294+
295+
296+#------------------------------------------------------------------------------
297+# open_port: Convenience function to open a port in juju to
298+# expose a service
299+#------------------------------------------------------------------------------
300+def open_port(port=None, protocol="TCP"):
301+ if port is None:
302+ return(None)
303+ return(subprocess.call(['/usr/bin/open-port', "%d/%s" % \
304+ (int(port), protocol)]))
305+
306+
307+#------------------------------------------------------------------------------
308+# close_port: Convenience function to close a port in juju to
309+# unexpose a service
310+#------------------------------------------------------------------------------
311+def close_port(port=None, protocol="TCP"):
312+ if port is None:
313+ return(None)
314+ return(subprocess.call(['/usr/bin/close-port', "%d/%s" % \
315+ (int(port), protocol)]))
316+
317+
318+#------------------------------------------------------------------------------
319+# update_service_ports: Convenience function that evaluate the old and new
320+# service ports to decide which ports need to be
321+# opened and which to close
322+#------------------------------------------------------------------------------
323+def update_service_ports(old_service_ports=None, new_service_ports=None):
324+ if old_service_ports is None or new_service_ports is None:
325+ return(None)
326+ for port in old_service_ports:
327+ if port not in new_service_ports:
328+ close_port(port)
329+ for port in new_service_ports:
330+ if port not in old_service_ports:
331+ open_port(port)
332+
333+
334+#------------------------------------------------------------------------------
335+# pwgen: Generates a random password
336+# pwd_length: Defines the length of the password to generate
337+# default: 20
338+#------------------------------------------------------------------------------
339+def pwgen(pwd_length=20):
340+ alphanumeric_chars = [l for l in (string.letters + string.digits) \
341+ if l not in 'Iil0oO1']
342+ random_chars = [random.choice(alphanumeric_chars) \
343+ for i in range(pwd_length)]
344+ return(''.join(random_chars))
345+
346+
347+#------------------------------------------------------------------------------
348+# create_listen_stanza: Function to create a generic listen section in the
349+# haproxy config
350+# service_name: Arbitrary service name
351+# service_ip: IP address to listen for connections
352+# service_port: Port to listen for connections
353+# service_options: Comma separated list of options
354+# server_entries: List of tuples
355+# server_name
356+# server_ip
357+# server_port
358+# server_options
359+#------------------------------------------------------------------------------
360+def create_listen_stanza(service_name=None, service_ip=None,
361+ service_port=None, service_options=None,
362+ server_entries=None):
363+ if service_name is None or service_ip is None or service_port is None:
364+ return(None)
365+ service_config = []
366+ service_config.append("listen %s %s:%s" % \
367+ (service_name, service_ip, service_port))
368+ if service_options is not None:
369+ for service_option in service_options:
370+ service_config.append(" %s" % service_option.strip())
371+ if server_entries is not None and type(server_entries) == type([]):
372+ for (server_name, server_ip, server_port, server_options) \
373+ in server_entries:
374+ server_line = " server %s %s:%s" % \
375+ (server_name, server_ip, server_port)
376+ if server_options is not None:
377+ server_line += " %s" % server_options
378+ service_config.append(server_line)
379+ return('\n'.join(service_config))
380+
381+
382+#------------------------------------------------------------------------------
383+# create_monitoring_stanza: Function to create the haproxy monitoring section
384+# service_name: Arbitrary name
385+#------------------------------------------------------------------------------
386+def create_monitoring_stanza(service_name="haproxy_monitoring"):
387+ config_data = config_get()
388+ if config_data['enable_monitoring'] is False:
389+ return(None)
390+ monitoring_password = get_monitoring_password()
391+ if config_data['monitoring_password'] != "changeme":
392+ monitoring_password = config_data['monitoring_password']
393+ elif monitoring_password is None and \
394+ config_data['monitoring_password'] == "changeme":
395+ monitoring_password = pwgen()
396+ monitoring_config = []
397+ monitoring_config.append("mode http")
398+ monitoring_config.append("acl allowed_cidr src %s" % \
399+ config_data['monitoring_allowed_cidr'])
400+ monitoring_config.append("block unless allowed_cidr")
401+ monitoring_config.append("stats enable")
402+ monitoring_config.append("stats uri /")
403+ monitoring_config.append("stats realm Haproxy\ Statistics")
404+ monitoring_config.append("stats auth %s:%s" % \
405+ (config_data['monitoring_username'], monitoring_password))
406+ monitoring_config.append("stats refresh %d" % \
407+ config_data['monitoring_stats_refresh'])
408+ return(create_listen_stanza(service_name, \
409+ "0.0.0.0", \
410+ config_data['monitoring_port'], \
411+ monitoring_config))
412+
413+
414+#------------------------------------------------------------------------------
415+# get_config_services: Convenience function that returns a list
416+# of dictionary entries containing all of the services
417+# configuration
418+#------------------------------------------------------------------------------
419+def get_config_services():
420+ config_data = config_get()
421+ services_list = yaml.load(config_data['services'])
422+ return(services_list)
423+
424+
425+#------------------------------------------------------------------------------
426+# create_services: Function that will create the services configuration
427+# from the config data and/or relation information
428+#------------------------------------------------------------------------------
429+def create_services():
430+ services_list = get_config_services()
431+ services_dict = {}
432+ for service_item in services_list:
433+ service_name = service_item['service_name']
434+ service_host = service_item['service_host']
435+ service_port = service_item['service_port']
436+ service_options = service_item['service_options']
437+ server_options = service_item['server_options']
438+ services_dict[service_name] = {'service_name': service_name,
439+ 'service_host': service_host,
440+ 'service_port': service_port,
441+ 'service_options': service_options,
442+ 'server_options': server_options}
443+
444+ try:
445+ for unit in json.loads(\
446+ subprocess.check_output(['relation-list', '--format=json'])):
447+ relation_info = relation_get(None, unit)
448+ if type(relation_info) != type({}):
449+ sys.exit(0)
450+ # Mandatory switches ( hostname, port )
451+ server_name = "%s__%s" % \
452+ (relation_info['hostname'].replace('.', '_'), \
453+ relation_info['port'])
454+ server_ip = relation_info['hostname']
455+ server_port = relation_info['port']
456+ # Optional switches ( service_name )
457+ if 'service_name' in relation_info:
458+ if relation_info['service_name'] in services_dict:
459+ service_name = relation_info['service_name']
460+ else:
461+ subprocess.call([\
462+ 'juju-log', 'service %s does not exists. ' % \
463+ relation_info['service_name']])
464+ sys.exit(1)
465+ else:
466+ service_name = services_list[0]['service_name']
467+ if os.path.exists("%s/%s.is.proxy" % \
468+ (default_haproxy_service_config_dir, service_name)):
469+ if 'option forwardfor' not in service_options:
470+ service_options.append("option forwardfor")
471+ # Add the server entries
472+ if not 'servers' in services_dict[service_name]:
473+ services_dict[service_name]['servers'] = \
474+ [(server_name, server_ip, server_port, \
475+ services_dict[service_name]['server_options'])]
476+ else:
477+ services_dict[service_name]['servers'].append((\
478+ server_name, server_ip, server_port, \
479+ services_dict[service_name]['server_options']))
480+ except:
481+ pass
482+ # Construct the new haproxy.cfg file
483+ for service in services_dict:
484+ print "Service: ", service
485+ server_entries = None
486+ if 'servers' in services_dict[service]:
487+ server_entries = services_dict[service]['servers']
488+ with open("%s/%s.service" % (\
489+ default_haproxy_service_config_dir, \
490+ services_dict[service]['service_name']), 'w') as service_config:
491+ service_config.write(\
492+ create_listen_stanza(services_dict[service]['service_name'],\
493+ services_dict[service]['service_host'],
494+ services_dict[service]['service_port'],
495+ services_dict[service]['service_options'],
496+ server_entries))
497+
498+
499+#------------------------------------------------------------------------------
500+# load_services: Convenience function that load the service snippet
501+# configuration from the filesystem.
502+#------------------------------------------------------------------------------
503+def load_services(service_name=None):
504+ services = ''
505+ if service_name is not None:
506+ if os.path.exists("%s/%s.service" % \
507+ (default_haproxy_service_config_dir, service_name)):
508+ services = open("%s/%s.service" % \
509+ (default_haproxy_service_config_dir, service_name)).read()
510+ else:
511+ services = None
512+ else:
513+ for service in glob.glob("%s/*.service" % \
514+ default_haproxy_service_config_dir):
515+ services += open(service).read()
516+ services += "\n\n"
517+ return(services)
518+
519+
520+#------------------------------------------------------------------------------
521+# remove_services: Convenience function that removes the configuration
522+# snippets from the filesystem. This is necessary
523+# To ensure sync between the config/relation-data
524+# and the existing haproxy services.
525+#------------------------------------------------------------------------------
526+def remove_services(service_name=None):
527+ if service_name is not None:
528+ if os.path.exists("%s/%s.service" % \
529+ (default_haproxy_service_config_dir, service_name)):
530+ try:
531+ os.remove("%s/%s.service" % \
532+ (default_haproxy_service_config_dir, service_name))
533+ return(True)
534+ except:
535+ return(False)
536+ else:
537+ for service in glob.glob("%s/*.service" % \
538+ default_haproxy_service_config_dir):
539+ try:
540+ os.remove(service)
541+ except:
542+ pass
543+ return(True)
544+
545+
546+#------------------------------------------------------------------------------
547+# construct_haproxy_config: Convenience function to write haproxy.cfg
548+# haproxy_globals, haproxy_defaults,
549+# haproxy_monitoring, haproxy_services
550+# are all strings that will be written without
551+# any checks.
552+# haproxy_monitoring and haproxy_services are
553+# optional arguments
554+#------------------------------------------------------------------------------
555+def construct_haproxy_config(haproxy_globals=None,
556+ haproxy_defaults=None,
557+ haproxy_monitoring=None,
558+ haproxy_services=None):
559+ if haproxy_globals is None or \
560+ haproxy_defaults is None:
561+ return(None)
562+ with open(default_haproxy_config, 'w') as haproxy_config:
563+ haproxy_config.write(haproxy_globals)
564+ haproxy_config.write("\n")
565+ haproxy_config.write("\n")
566+ haproxy_config.write(haproxy_defaults)
567+ haproxy_config.write("\n")
568+ haproxy_config.write("\n")
569+ if haproxy_monitoring is not None:
570+ haproxy_config.write(haproxy_monitoring)
571+ haproxy_config.write("\n")
572+ haproxy_config.write("\n")
573+ if haproxy_services is not None:
574+ haproxy_config.write(haproxy_services)
575+ haproxy_config.write("\n")
576+ haproxy_config.write("\n")
577+
578+
579+#------------------------------------------------------------------------------
580+# service_haproxy: Convenience function to start/stop/restart/reload
581+# the haproxy service
582+#------------------------------------------------------------------------------
583+def service_haproxy(action=None, haproxy_config=default_haproxy_config):
584+ if action is None or haproxy_config is None:
585+ return(None)
586+ elif action == "check":
587+ retVal = subprocess.call(\
588+ ['/usr/sbin/haproxy', '-f', haproxy_config, '-c'])
589+ if retVal == 1:
590+ return(False)
591+ elif retVal == 0:
592+ return(True)
593+ else:
594+ return(False)
595+ else:
596+ retVal = subprocess.call(['service', 'haproxy', action])
597+ if retVal == 0:
598+ return(True)
599+ else:
600+ return(False)
601+
602+
603+###############################################################################
604+# Hook functions
605+###############################################################################
606+def install_hook():
607+ if not os.path.exists(default_haproxy_service_config_dir):
608+ os.mkdir(default_haproxy_service_config_dir, 0600)
609+ return (apt_get_install("haproxy") == enable_haproxy() == True)
610+
611+
612+def config_changed():
613+ config_data = config_get()
614+ current_service_ports = get_service_ports()
615+ haproxy_globals = create_haproxy_globals()
616+ haproxy_defaults = create_haproxy_defaults()
617+ if config_data['enable_monitoring'] is True:
618+ haproxy_monitoring = create_monitoring_stanza()
619+ else:
620+ haproxy_monitoring = None
621+ remove_services()
622+ create_services()
623+ haproxy_services = load_services()
624+ construct_haproxy_config(haproxy_globals, \
625+ haproxy_defaults, \
626+ haproxy_monitoring, \
627+ haproxy_services)
628+
629+ if service_haproxy("check"):
630+ updated_service_ports = get_service_ports()
631+ update_service_ports(current_service_ports, updated_service_ports)
632+ service_haproxy("reload")
633+
634+
635+def start_hook():
636+ if service_haproxy("status"):
637+ return(service_haproxy("restart"))
638+ else:
639+ return(service_haproxy("start"))
640+
641+
642+def stop_hook():
643+ if service_haproxy("status"):
644+ return(service_haproxy("stop"))
645+
646+
647+def reverseproxy_interface(hook_name=None):
648+ if hook_name is None:
649+ return(None)
650+ if hook_name == "changed":
651+ config_changed()
652+
653+
654+def website_interface(hook_name=None):
655+ if hook_name is None:
656+ return(None)
657+ my_fqdn = socket.getfqdn(socket.gethostname())
658+ default_port = 80
659+ relation_data = relation_get()
660+ if hook_name == "joined":
661+ subprocess.call(['relation-set', 'port=%d' % \
662+ default_port, 'hostname=%s' % my_fqdn])
663+ elif hook_name == "changed":
664+ if 'is-proxy' in relation_data:
665+ service_name = "%s__%d" % \
666+ (relation_data['hostname'], relation_data['port'])
667+ open("%s/%s.is.proxy" % \
668+ (default_haproxy_service_config_dir, service_name), 'a').close()
669+
670+
671+###############################################################################
672+# Main section
673+###############################################################################
674+if hook_name == "install":
675+ install_hook()
676+elif hook_name == "config-changed":
677+ config_changed()
678+elif hook_name == "start":
679+ start_hook()
680+elif hook_name == "stop":
681+ stop_hook()
682+elif hook_name == "reverseproxy-relation-broken":
683+ config_changed()
684+elif hook_name == "reverseproxy-relation-changed":
685+ reverseproxy_interface("changed")
686+elif hook_name == "website-relation-joined":
687+ website_interface("joined")
688+elif hook_name == "website-relation-changed":
689+ website_interface("changed")
690+else:
691+ print "Unknown hook"
692+ sys.exit(1)
693
694=== modified file 'hooks/install'
695--- hooks/install 2011-02-16 01:17:47 +0000
696+++ hooks/install 1970-01-01 00:00:00 +0000
697@@ -1,11 +0,0 @@
698-#!/bin/bash
699-
700-set -e
701-
702-DEBIAN_FRONTEND=noninteractive apt-get -y install -qq haproxy
703-
704-cat > /etc/default/haproxy <<EOF
705-ENABLED=1
706-EOF
707-# We actually want it stopped until at least one thing has joined
708-service haproxy stop
709
710=== target is u'./hooks.py'
711=== added symlink 'hooks/reverseproxy-relation-broken'
712=== target is u'./hooks.py'
713=== modified file 'hooks/reverseproxy-relation-changed'
714--- hooks/reverseproxy-relation-changed 2011-09-30 20:13:29 +0000
715+++ hooks/reverseproxy-relation-changed 1970-01-01 00:00:00 +0000
716@@ -1,114 +0,0 @@
717-#!/usr/bin/env python
718-#
719-# reverseproxy-relation-changed - hook for when reverse proxy relation changes
720-#
721-# Copyright (C) 2011 Canonical Ltd.
722-# Author: Clint Byrum <clint.byrum@canonical.com>
723-#
724-# This program is free software: you can redistribute it and/or modify
725-# it under the terms of the GNU General Public License as published by
726-# the Free Software Foundation, either version 3 of the License, or
727-# (at your option) any later version.
728-#
729-# This program is distributed in the hope that it will be useful,
730-# but WITHOUT ANY WARRANTY; without even the implied warranty of
731-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
732-# GNU General Public License for more details.
733-#
734-# You should have received a copy of the GNU General Public License
735-# along with this program. If not, see <http://www.gnu.org/licenses/>.
736-#
737-
738-import sys
739-import os
740-import subprocess
741-import json
742-import tempfile
743-import glob
744-
745-from socket import getaddrinfo
746-
747-remote_unit = os.environ.get("JUJU_REMOTE_UNIT")
748-
749-service_name, _ = remote_unit.split("/")
750-
751-# TODO: maybe load this from disk for easier customization
752-template = """
753-# Generated by juju
754-# this config needs haproxy-1.1.28 or haproxy-1.2.1
755-
756-global
757- log 127.0.0.1 local0
758- log 127.0.0.1 local1 notice
759- #log loghost local0 info
760- maxconn 4096
761- #chroot /usr/share/haproxy
762- user haproxy
763- group haproxy
764- daemon
765- #debug
766- #quiet
767-
768-defaults
769- log global
770- mode http
771- option httplog
772- option dontlognull
773- retries 3
774- option redispatch
775- maxconn 2000
776- contimeout 5000
777- clitimeout 50000
778- srvtimeout 50000
779-
780-listen %s 0.0.0.0:80
781- option httpchk *
782- balance roundrobin
783-"""
784-
785-units = []
786-p = subprocess.Popen("relation-list", stdout=subprocess.PIPE)
787-for unit in p.stdout:
788- units.append(unit.strip())
789-
790-print units
791-
792-# Right now we don't know how to connect fronts to backs, but we're going to say
793-# if there are any upstream proxies we will turn off forwardedfor
794-options = ''
795-proxy_services = glob.glob("/etc/haproxy/*.is.proxy")
796-if len(proxy_services) == 0:
797- options += " option forwardfor\n"
798-
799-servers = ''
800-for unit in units:
801- p = subprocess.Popen(["relation-get", "--format", "json", "-", unit],
802- stdout=subprocess.PIPE, close_fds=True)
803- settings = json.loads(p.stdout.read().strip())
804- p.wait()
805- # Add all configured units:
806- if 'hostname' in settings and 'port' in settings:
807- servers += (" server %(hostname)s %(hostname)s:%(port)s check\n" % settings)
808-
809-print servers
810-
811-with tempfile.NamedTemporaryFile(dir="/etc/haproxy",prefix="haproxy.cfg", delete=False) as conf:
812- conf.write((template % service_name) + options + servers)
813- try:
814- os.unlink("/etc/haproxy/haproxy.cfg.old")
815- except:
816- pass
817- try:
818- os.rename("/etc/haproxy/haproxy.cfg","/etc/haproxy/haproxy.old")
819- except:
820- pass
821- try:
822- os.rename(conf.name, "/etc/haproxy/haproxy.cfg")
823- except:
824- os.unlink(conf.name)
825-
826-# Just in case haproxy wouldn't start because of empty/bad configs before, start it now
827-subprocess.call(["service", "haproxy", "start"])
828-subprocess.call(["service", "haproxy", "reload"])
829-
830-subprocess.call(["open-port", "80"])
831
832=== target is u'./hooks.py'
833=== modified file 'hooks/start'
834--- hooks/start 2011-02-16 01:17:47 +0000
835+++ hooks/start 1970-01-01 00:00:00 +0000
836@@ -1,1 +0,0 @@
837-#!/bin/bash
838\ No newline at end of file
839
840=== target is u'./hooks.py'
841=== modified file 'hooks/stop'
842--- hooks/stop 2012-06-27 16:48:38 +0000
843+++ hooks/stop 1970-01-01 00:00:00 +0000
844@@ -1,3 +0,0 @@
845-#!/bin/bash
846-
847-/etc/init.d/haproxy stop
848
849=== target is u'./hooks.py'
850=== modified file 'hooks/website-relation-changed'
851--- hooks/website-relation-changed 2011-09-30 20:13:29 +0000
852+++ hooks/website-relation-changed 1970-01-01 00:00:00 +0000
853@@ -1,8 +0,0 @@
854-#!/bin/sh
855-
856-service=`echo $JUJU_REMOTE_UNIT | cut -d/ -f1`
857-isproxy=`relation-get is-proxy`
858-
859-if [ -n "$isproxy" ] ; then
860- touch /etc/haproxy/$service.is.proxy
861-fi
862
863=== target is u'./hooks.py'
864=== modified file 'hooks/website-relation-joined'
865--- hooks/website-relation-joined 2011-06-15 23:55:56 +0000
866+++ hooks/website-relation-joined 1970-01-01 00:00:00 +0000
867@@ -1,2 +0,0 @@
868-#!/bin/sh
869-relation-set port=80 hostname=`hostname -f`
870
871=== target is u'./hooks.py'
872=== modified file 'metadata.yaml'
873--- metadata.yaml 2012-05-22 22:29:14 +0000
874+++ metadata.yaml 2012-07-13 21:44:18 +0000
875@@ -1,6 +1,6 @@
876 name: haproxy
877 summary: "fast and reliable load balancing reverse proxy"
878-maintainer: Clint Byrum <clint@ubuntu.com>
879+maintainer: Juan Negron <juan@ubuntu.com>, Tom Haddon <tom.haddon@canonical.com>
880 description:
881 HAProxy is a TCP/HTTP reverse proxy which is particularly suited for high
882 availability environments. It features connection persistence through HTTP
883
884=== modified file 'revision'
885--- revision 2011-10-11 19:17:16 +0000
886+++ revision 2012-07-13 21:44:18 +0000
887@@ -1,1 +1,1 @@
888-15
889+22

Subscribers

People subscribed via source and target branches