Merge lp:~stub/charms/precise/postgresql/use-charm-helpers into lp:charms/postgresql

Proposed by Stuart Bishop
Status: Merged
Approved by: Mark Mims
Approved revision: 84
Merged at revision: 55
Proposed branch: lp:~stub/charms/precise/postgresql/use-charm-helpers
Merge into: lp:charms/postgresql
Prerequisite: lp:~stub/charms/precise/postgresql/replication
Diff against target: 1468 lines (+295/-571)
2 files modified
hooks/hooks.py (+283/-569)
test.py (+12/-2)
To merge this branch: bzr merge lp:~stub/charms/precise/postgresql/use-charm-helpers
Reviewer Review Type Date Requested Status
Mark Mims (community) Approve
Review via email: mp+173470@code.launchpad.net

Description of the change

Use charm-helpers everywhere appropriate, removing our own unnecessary and untested helpers.

To post a comment you must log in.
84. By Stuart Bishop

Merged replication into use-charm-helpers.

Revision history for this message
Mark Mims (mark-mims) wrote :

sweet! looks good... love removing code.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'hooks/hooks.py'
2--- hooks/hooks.py 2013-07-08 11:07:29 +0000
3+++ hooks/hooks.py 2013-07-08 11:07:29 +0000
4@@ -5,7 +5,6 @@
5 import cPickle as pickle
6 import glob
7 from grp import getgrnam
8-import json
9 import os.path
10 from pwd import getpwnam
11 import random
12@@ -15,16 +14,16 @@
13 import string
14 import subprocess
15 import sys
16-from textwrap import dedent
17 import time
18 import yaml
19 from yaml.constructor import ConstructorError
20
21-from charmhelpers.core import hookenv
22-
23+from charmhelpers.core import hookenv, host
24 from charmhelpers.core.hookenv import (
25- log, CRITICAL, ERROR, WARNING, INFO, DEBUG)
26+ CRITICAL, ERROR, WARNING, INFO, DEBUG, log,
27+ )
28
29+hooks = hookenv.Hooks()
30
31 # jinja2 may not be importable until the install hook has installed the
32 # required packages.
33@@ -33,23 +32,26 @@
34 return Template(*args, **kw)
35
36
37-###############################################################################
38-# Supporting functions
39-###############################################################################
40-MSG_CRITICAL = "CRITICAL"
41-MSG_DEBUG = "DEBUG"
42-MSG_INFO = "INFO"
43-MSG_ERROR = "ERROR"
44-MSG_WARNING = "WARNING"
45-
46-
47-def juju_log(level, msg):
48- log(msg, level)
49+def write_file(path, contents, owner='root', group='root', perms=0o444):
50+ '''Temporary alternative to charm-helpers write_file().
51+
52+ charm-helpers' write_file() magic makes it useless for any file
53+ containing curly brackets, so work around for now until the feature
54+ can be discussed.
55+ '''
56+ log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
57+ uid = getpwnam(owner).pw_uid
58+ gid = getgrnam(group).gr_gid
59+ dest_fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, perms)
60+ os.fchown(dest_fd, uid, gid)
61+ with os.fdopen(dest_fd, 'w') as destfile:
62+ destfile.write(str(contents))
63
64
65 class State(dict):
66 """Encapsulate state common to the unit for republishing to relations."""
67 def __init__(self, state_file):
68+ super(State, self).__init__()
69 self._state_file = state_file
70 self.load()
71
72@@ -103,7 +105,6 @@
73
74
75 ###############################################################################
76-
77 # Volume managment
78 ###############################################################################
79 #------------------------------
80@@ -119,7 +120,7 @@
81 if volume_map:
82 return volume_map.get(os.environ['JUJU_UNIT_NAME'])
83 except ConstructorError as e:
84- juju_log(MSG_WARNING, "invalid YAML in 'volume-map': %s", e)
85+ log("invalid YAML in 'volume-map': {}".format(e), WARNING)
86 return None
87
88
89@@ -150,18 +151,21 @@
90 def volume_get_volume_id():
91 ephemeral_storage = config_data['volume-ephemeral-storage']
92 volid = volume_get_volid_from_volume_map()
93- juju_unit_name = os.environ['JUJU_UNIT_NAME']
94+ juju_unit_name = hookenv.local_unit()
95 if ephemeral_storage in [True, 'yes', 'Yes', 'true', 'True']:
96 if volid:
97- juju_log(MSG_ERROR, "volume-ephemeral-storage is True, but " +
98- "volume-map['%s'] -> %s" % (juju_unit_name, volid))
99+ log(
100+ "volume-ephemeral-storage is True, but " +
101+ "volume-map[{!r}] -> {}".format(juju_unit_name, volid), ERROR)
102 return None
103 else:
104 return "--ephemeral"
105 else:
106 if not volid:
107- juju_log(MSG_ERROR, "volume-ephemeral-storage is False, but " +
108- "no volid found for volume-map['%s']" % (juju_unit_name))
109+ log(
110+ "volume-ephemeral-storage is False, but "
111+ "no volid found for volume-map[{!r}]".format(
112+ hookenv.local_unit()), ERROR)
113 return None
114 return volid
115
116@@ -191,7 +195,7 @@
117 def enable_service_start(service):
118 ### NOTE: doesn't implement per-service, this can be an issue
119 ### for colocated charms (subordinates)
120- juju_log(MSG_INFO, "NOTICE: enabling %s start by policy-rc.d" % service)
121+ log("enabling {} start by policy-rc.d".format(service))
122 if os.path.exists('/usr/sbin/policy-rc.d'):
123 os.unlink('/usr/sbin/policy-rc.d')
124 return True
125@@ -199,10 +203,10 @@
126
127
128 def disable_service_start(service):
129- juju_log(MSG_INFO, "NOTICE: disabling %s start by policy-rc.d" % service)
130+ log("disabling {} start by policy-rc.d".format(service))
131 policy_rc = '/usr/sbin/policy-rc.d'
132- policy_rc_tmp = "%s.tmp" % policy_rc
133- open('%s' % policy_rc_tmp, 'w').write("""#!/bin/bash
134+ policy_rc_tmp = "{}.tmp".format(policy_rc)
135+ open(policy_rc_tmp, 'w').write("""#!/bin/bash
136 [[ "$1"-"$2" == %s-start ]] && exit 101
137 exit 0
138 EOF
139@@ -211,16 +215,14 @@
140 os.rename(policy_rc_tmp, policy_rc)
141
142
143-#------------------------------------------------------------------------------
144-# run: Run a command, return the output
145-#------------------------------------------------------------------------------
146 def run(command, exit_on_error=True):
147+ '''Run a command and return the output.'''
148 try:
149- juju_log(MSG_DEBUG, command)
150+ log(command, DEBUG)
151 return subprocess.check_output(
152 command, stderr=subprocess.STDOUT, shell=True)
153 except subprocess.CalledProcessError, e:
154- juju_log(MSG_ERROR, "status=%d, output=%s" % (e.returncode, e.output))
155+ log("status=%d, output=%s" % (e.returncode, e.output), ERROR)
156 if exit_on_error:
157 sys.exit(e.returncode)
158 else:
159@@ -228,27 +230,6 @@
160
161
162 #------------------------------------------------------------------------------
163-# install_file: install a file resource. overwites existing files.
164-#------------------------------------------------------------------------------
165-def install_file(contents, dest, owner="root", group="root", mode=0600):
166- uid = getpwnam(owner)[2]
167- gid = getgrnam(group)[2]
168- dest_fd = os.open(dest, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
169- os.fchown(dest_fd, uid, gid)
170- with os.fdopen(dest_fd, 'w') as destfile:
171- destfile.write(str(contents))
172-
173-
174-#------------------------------------------------------------------------------
175-# install_dir: create a directory
176-#------------------------------------------------------------------------------
177-def install_dir(dirname, owner="root", group="root", mode=0700):
178- command = '/usr/bin/install -o {} -g {} -m {} -d {}'.format(
179- owner, group, oct(mode), dirname)
180- return run(command)
181-
182-
183-#------------------------------------------------------------------------------
184 # postgresql_stop, postgresql_start, postgresql_is_running:
185 # wrappers over invoke-rc.d, with extra check for postgresql_is_running()
186 #------------------------------------------------------------------------------
187@@ -272,7 +253,7 @@
188 def postgresql_start():
189 status, output = commands.getstatusoutput("invoke-rc.d postgresql start")
190 if status != 0:
191- juju_log(MSG_CRITICAL, output)
192+ log(output, CRITICAL)
193 return False
194 return postgresql_is_running()
195
196@@ -287,11 +268,8 @@
197 last_warning = time.time()
198 while postgresql_is_in_backup_mode():
199 if time.time() + 120 > last_warning:
200- juju_log(
201- MSG_WARNING,
202- "In backup mode. PostgreSQL restart blocked.")
203- juju_log(
204- MSG_INFO,
205+ log("In backup mode. PostgreSQL restart blocked.", WARNING)
206+ log(
207 "Run \"psql -U postgres -c 'SELECT pg_stop_backup()'\""
208 "to cancel backup mode and forcefully unblock this hook.")
209 last_warning = time.time()
210@@ -346,9 +324,8 @@
211
212 if new_value != live_value:
213 if live_config:
214- juju_log(
215- MSG_DEBUG, "Changed {} from {} to {}".format(
216- name, repr(live_value), repr(new_value)))
217+ log("Changed {} from {!r} to {!r}".format(
218+ name, live_value, new_value), DEBUG)
219 if context == 'postmaster':
220 # A setting has changed that requires PostgreSQL to be
221 # restarted before it will take effect.
222@@ -356,14 +333,12 @@
223
224 if requires_restart:
225 # A change has been requested that requires a restart.
226- juju_log(
227- MSG_WARNING,
228- "Configuration change requires PostgreSQL restart. "
229- "Restarting.")
230+ log(
231+ "Configuration change requires PostgreSQL restart. Restarting.",
232+ WARNING)
233 rc = postgresql_restart()
234 else:
235- juju_log(
236- MSG_DEBUG, "PostgreSQL reload, config changes taking effect.")
237+ log("PostgreSQL reload, config changes taking effect.", DEBUG)
238 rc = postgresql_reload() # No pending need to bounce, just reload.
239
240 if rc == 0 and 'saved_config' in local_state:
241@@ -373,178 +348,20 @@
242 return rc
243
244
245-#------------------------------------------------------------------------------
246-# config_get: Returns a dictionary containing all of the config information
247-# Optional parameter: scope
248-# scope: limits the scope of the returned configuration to the
249-# desired config item.
250-#------------------------------------------------------------------------------
251-def config_get(scope=None):
252- try:
253- config_cmd_line = ['config-get']
254- if scope is not None:
255- config_cmd_line.append(scope)
256- config_cmd_line.append('--format=json')
257- config_data = json.loads(subprocess.check_output(config_cmd_line))
258- except:
259- config_data = None
260- finally:
261- return(config_data)
262-
263-
264-#------------------------------------------------------------------------------
265-# get_service_port: Convenience function that scans the existing postgresql
266-# configuration file and returns a the existing port
267-# being used. This is necessary to know which port(s)
268-# to open and close when exposing/unexposing a service
269-#------------------------------------------------------------------------------
270 def get_service_port(postgresql_config):
271- postgresql_config = load_postgresql_config(postgresql_config)
272- if postgresql_config is None:
273- return(None)
274+ '''Return the port PostgreSQL is listening on.'''
275+ if not os.path.exists(postgresql_config):
276+ return None
277+ postgresql_config = open(postgresql_config, 'r').read()
278 port = re.search("port.*=(.*)", postgresql_config).group(1).strip()
279 try:
280 return int(port)
281- except:
282- return None
283-
284-
285-#------------------------------------------------------------------------------
286-# relation_json: Returns json-formatted relation data
287-# Optional parameters: scope, relation_id
288-# scope: limits the scope of the returned data to the
289-# desired item.
290-# unit_name: limits the data ( and optionally the scope )
291-# to the specified unit
292-# relation_id: specify relation id for out of context usage.
293-#------------------------------------------------------------------------------
294-def relation_json(scope=None, unit_name=None, relation_id=None):
295- command = ['relation-get', '--format=json']
296- if relation_id is not None:
297- command.extend(('-r', relation_id))
298- if scope is not None:
299- command.append(scope)
300- else:
301- command.append('-')
302- if unit_name is not None:
303- command.append(unit_name)
304- output = subprocess.check_output(command, stderr=subprocess.STDOUT)
305- return output or None
306-
307-
308-#------------------------------------------------------------------------------
309-# relation_get: Returns a dictionary containing the relation information
310-# Optional parameters: scope, relation_id
311-# scope: limits the scope of the returned data to the
312-# desired item.
313-# unit_name: limits the data ( and optionally the scope )
314-# to the specified unit
315-#------------------------------------------------------------------------------
316-def relation_get(scope=None, unit_name=None, relation_id=None):
317- j = relation_json(scope, unit_name, relation_id)
318- if j:
319- return json.loads(j)
320- else:
321- return None
322-
323-
324-def relation_set(keyvalues, relation_id=None):
325- args = []
326- if relation_id:
327- args.extend(['-r', relation_id])
328- args.extend(["{}='{}'".format(k, v or '') for k, v in keyvalues.items()])
329- run("relation-set {}".format(' '.join(args)))
330-
331- ## Posting json to relation-set doesn't seem to work as documented?
332- ## Bug #1116179
333- ##
334- ## cmd = ['relation-set']
335- ## if relation_id:
336- ## cmd.extend(['-r', relation_id])
337- ## p = Popen(
338- ## cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
339- ## stderr=subprocess.PIPE)
340- ## (out, err) = p.communicate(json.dumps(keyvalues))
341- ## if p.returncode:
342- ## juju_log(MSG_ERROR, err)
343- ## sys.exit(1)
344- ## juju_log(MSG_DEBUG, "relation-set {}".format(repr(keyvalues)))
345-
346-
347-def relation_list(relation_id=None):
348- """Return the list of units participating in the relation."""
349- if relation_id is None:
350- relation_id = os.environ['JUJU_RELATION_ID']
351- cmd = ['relation-list', '--format=json', '-r', relation_id]
352- json_units = subprocess.check_output(cmd).strip()
353- if json_units:
354- data = json.loads(json_units)
355- if data is not None:
356- return data
357- return []
358-
359-
360-#------------------------------------------------------------------------------
361-# relation_ids: Returns a list of relation ids
362-# optional parameters: relation_type
363-# relation_type: return relations only of this type
364-#------------------------------------------------------------------------------
365-def relation_ids(relation_types=('db',)):
366- # accept strings or iterators
367- if isinstance(relation_types, basestring):
368- reltypes = [relation_types, ]
369- else:
370- reltypes = relation_types
371- relids = []
372- for reltype in reltypes:
373- relid_cmd_line = ['relation-ids', '--format=json', reltype]
374- json_relids = subprocess.check_output(relid_cmd_line).strip()
375- if json_relids:
376- relids.extend(json.loads(json_relids))
377- return relids
378-
379-
380-#------------------------------------------------------------------------------
381-# relation_get_all: Returns a dictionary containing the relation information
382-# optional parameters: relation_type
383-# relation_type: limits the scope of the returned data to the
384-# desired item.
385-#------------------------------------------------------------------------------
386-def relation_get_all(*args, **kwargs):
387- relation_data = []
388- relids = relation_ids(*args, **kwargs)
389- for relid in relids:
390- units_cmd_line = ['relation-list', '--format=json', '-r', relid]
391- json_units = subprocess.check_output(units_cmd_line).strip()
392- if json_units:
393- for unit in json.loads(json_units):
394- unit_data = \
395- json.loads(relation_json(relation_id=relid,
396- unit_name=unit))
397- for key in unit_data:
398- if key.endswith('-list'):
399- unit_data[key] = unit_data[key].split()
400- unit_data['relation-id'] = relid
401- unit_data['unit'] = unit
402- relation_data.append(unit_data)
403- return relation_data
404-
405-
406-#------------------------------------------------------------------------------
407-# apt_get_install( packages ): Installs package(s)
408-#------------------------------------------------------------------------------
409-def apt_get_install(packages=None):
410- if packages is None:
411- return(False)
412- cmd_line = ['apt-get', '-y', 'install', '-qq']
413- cmd_line.extend(packages)
414- return(subprocess.call(cmd_line))
415-
416-
417-#------------------------------------------------------------------------------
418-# create_postgresql_config: Creates the postgresql.conf file
419-#------------------------------------------------------------------------------
420+ except (ValueError, TypeError):
421+ return None
422+
423+
424 def create_postgresql_config(postgresql_config):
425+ '''Create the postgresql.conf file'''
426 if config_data["performance_tuning"] == "auto":
427 # Taken from:
428 # http://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server
429@@ -575,9 +392,8 @@
430 # certain minimum levels.
431 num_slaves = slave_count()
432 if num_slaves > 0:
433- juju_log(
434- MSG_INFO, '{} hot standbys in peer relation.'.format(num_slaves))
435- juju_log(MSG_INFO, 'Ensuring minimal replication settings')
436+ log('{} hot standbys in peer relation.'.format(num_slaves))
437+ log('Ensuring minimal replication settings')
438 config_data['hot_standby'] = 'on'
439 config_data['wal_level'] = 'hot_standby'
440 config_data['max_wal_senders'] = max(
441@@ -590,29 +406,27 @@
442 # Return it as pg_config
443 pg_config = Template(
444 open("templates/postgresql.conf.tmpl").read()).render(config_data)
445- install_file(pg_config, postgresql_config)
446+ write_file(
447+ postgresql_config, pg_config,
448+ owner="postgres", group="postgres", perms=0600)
449
450 local_state['saved_config'] = config_data
451 local_state.save()
452
453
454-#------------------------------------------------------------------------------
455-# create_postgresql_ident: Creates the pg_ident.conf file
456-#------------------------------------------------------------------------------
457 def create_postgresql_ident(postgresql_ident):
458+ '''Create the pg_ident.conf file.'''
459 ident_data = {}
460- pg_ident_template = \
461- Template(
462- open("templates/pg_ident.conf.tmpl").read()).render(ident_data)
463- with open(postgresql_ident, 'w') as ident_file:
464- ident_file.write(str(pg_ident_template))
465-
466-
467-#------------------------------------------------------------------------------
468-# generate_postgresql_hba: Creates the pg_hba.conf file
469-#------------------------------------------------------------------------------
470-def generate_postgresql_hba(postgresql_hba, user=None,
471- schema_user=None, database=None):
472+ pg_ident_template = Template(
473+ open("templates/pg_ident.conf.tmpl").read())
474+ write_file(
475+ postgresql_ident, pg_ident_template.render(ident_data),
476+ owner="postgres", group="postgres", perms=0600)
477+
478+
479+def generate_postgresql_hba(
480+ postgresql_hba, user=None, schema_user=None, database=None):
481+ '''Create the pg_hba.conf file.'''
482
483 # Per Bug #1117542, when generating the postgresql_hba file we
484 # need to cope with private-address being either an IP address
485@@ -627,9 +441,10 @@
486 return addr
487
488 relation_data = []
489- for relid in relation_ids(relation_types=['db', 'db-admin']):
490- local_relation = relation_get(
491- unit_name=os.environ['JUJU_UNIT_NAME'], relation_id=relid)
492+ relids = hookenv.relation_ids('db') + hookenv.relation_ids('db-admin')
493+ for relid in relids:
494+ local_relation = hookenv.relation_get(
495+ unit=hookenv.local_unit(), rid=relid)
496
497 # We might see relations that have not yet been setup enough.
498 # At a minimum, the relation-joined hook needs to have been run
499@@ -638,8 +453,8 @@
500 if 'user' not in local_relation:
501 continue
502
503- for unit in relation_list(relid):
504- relation = relation_get(unit_name=unit, relation_id=relid)
505+ for unit in hookenv.related_units(relid):
506+ relation = hookenv.relation_get(unit=unit, rid=relid)
507
508 relation['relation-id'] = relid
509 relation['unit'] = unit
510@@ -666,15 +481,15 @@
511 relation['private-address'])
512 relation_data.append(relation)
513
514- juju_log(MSG_INFO, str(relation_data))
515+ log(str(relation_data), INFO)
516
517 # Replication connections. Each unit needs to be able to connect to
518 # every other unit's postgres database and the magic replication
519 # database. It also needs to be able to connect to its own postgres
520 # database.
521- for relid in relation_ids(relation_types=replication_relation_types):
522- for unit in relation_list(relid):
523- relation = relation_get(unit_name=unit, relation_id=relid)
524+ for relid in hookenv.relation_ids('replication'):
525+ for unit in hookenv.related_units(relid):
526+ relation = hookenv.relation_get(unit=unit, rid=relid)
527 remote_addr = munge_address(relation['private-address'])
528 remote_replication = {'database': 'replication',
529 'user': 'juju_replication',
530@@ -692,27 +507,25 @@
531 relation_data.append(remote_pgdb)
532
533 # Hooks need permissions too to setup replication.
534- for relid in relation_ids(relation_types=['replication']):
535+ for relid in hookenv.relation_ids('replication'):
536 local_replication = {'database': 'postgres',
537 'user': 'juju_replication',
538- 'private-address': munge_address(get_unit_host()),
539+ 'private-address': munge_address(
540+ hookenv.unit_private_ip()),
541 'relation-id': relid,
542- 'unit': os.environ['JUJU_UNIT_NAME'],
543+ 'unit': hookenv.local_unit(),
544 }
545 relation_data.append(local_replication)
546
547- pg_hba_template = Template(
548- open("templates/pg_hba.conf.tmpl").read()).render(
549- access_list=relation_data)
550- with open(postgresql_hba, 'w') as hba_file:
551- hba_file.write(str(pg_hba_template))
552+ pg_hba_template = Template(open("templates/pg_hba.conf.tmpl").read())
553+ write_file(
554+ postgresql_hba, pg_hba_template.render(access_list=relation_data),
555+ owner="postgres", group="postgres", perms=0600)
556 postgresql_reload()
557
558
559-#------------------------------------------------------------------------------
560-# install_postgresql_crontab: Creates the postgresql crontab file
561-#------------------------------------------------------------------------------
562 def install_postgresql_crontab(postgresql_ident):
563+ '''Create the postgres user's crontab'''
564 crontab_data = {
565 'backup_schedule': config_data["backup_schedule"],
566 'scripts_dir': postgresql_scripts_dir,
567@@ -720,7 +533,28 @@
568 }
569 crontab_template = Template(
570 open("templates/postgres.cron.tmpl").read()).render(crontab_data)
571- install_file(str(crontab_template), "/etc/cron.d/postgres", mode=0644)
572+ write_file('/etc/cron.d/postgres', crontab_template, perms=0600)
573+
574+
575+def create_recovery_conf(master_host, password, restart_on_change=False):
576+ recovery_conf_path = os.path.join(postgresql_cluster_dir, 'recovery.conf')
577+ if os.path.exists(recovery_conf_path):
578+ old_recovery_conf = open(recovery_conf_path, 'r').read()
579+ else:
580+ old_recovery_conf = None
581+
582+ recovery_conf = Template(
583+ open("templates/recovery.conf.tmpl").read()).render({
584+ 'host': master_host,
585+ 'password': local_state['replication_password']})
586+ log(recovery_conf, DEBUG)
587+ write_file(
588+ os.path.join(postgresql_cluster_dir, 'recovery.conf'),
589+ recovery_conf, owner="postgres", group="postgres", perms=0o600)
590+
591+ if restart_on_change and old_recovery_conf != recovery_conf:
592+ log("recovery.conf updated. Restarting to take effect.")
593+ postgresql_restart()
594
595
596 #------------------------------------------------------------------------------
597@@ -737,28 +571,6 @@
598
599
600 #------------------------------------------------------------------------------
601-# open_port: Convenience function to open a port in juju to
602-# expose a service
603-#------------------------------------------------------------------------------
604-def open_port(port=None, protocol="TCP"):
605- if port is None:
606- return(None)
607- return(subprocess.call(['open-port', "%d/%s" %
608- (int(port), protocol)]))
609-
610-
611-#------------------------------------------------------------------------------
612-# close_port: Convenience function to close a port in juju to
613-# unexpose a service
614-#------------------------------------------------------------------------------
615-def close_port(port=None, protocol="TCP"):
616- if port is None:
617- return(None)
618- return(subprocess.call(['close-port', "%d/%s" %
619- (int(port), protocol)]))
620-
621-
622-#------------------------------------------------------------------------------
623 # update_service_ports: Convenience function that evaluate the old and new
624 # service ports to decide which ports need to be
625 # opened and which to close
626@@ -767,18 +579,14 @@
627 if old_service_port is None or new_service_port is None:
628 return(None)
629 if new_service_port != old_service_port:
630- close_port(old_service_port)
631- open_port(new_service_port)
632-
633-
634-#------------------------------------------------------------------------------
635-# pwgen: Generates a random password
636-# pwd_length: Defines the length of the password to generate
637-# default: 20
638-#------------------------------------------------------------------------------
639+ hookenv.close_port(old_service_port)
640+ hookenv.open_port(new_service_port)
641+
642+
643 def pwgen(pwd_length=None):
644+ '''Generate a random password.'''
645 if pwd_length is None:
646- pwd_length = random.choice(range(20, 30))
647+ pwd_length = random.choice(range(30, 40))
648 alphanumeric_chars = [l for l in (string.letters + string.digits)
649 if l not in 'Iil0oO1']
650 random_chars = [random.choice(alphanumeric_chars)
651@@ -825,9 +633,8 @@
652 break
653 except psycopg2.Error, x:
654 if time.time() > start + timeout:
655- juju_log(
656- MSG_CRITICAL, "Database connection {!r} failed".format(
657- conn_str))
658+ log("Database connection {!r} failed".format(
659+ conn_str), CRITICAL)
660 raise
661 log("Unable to open connection ({}), retrying.".format(x))
662 time.sleep(1)
663@@ -842,7 +649,7 @@
664 cur.execute(sql, parameters)
665 return cur.statusmessage
666 except psycopg2.ProgrammingError:
667- juju_log(MSG_CRITICAL, sql)
668+ log(sql, CRITICAL)
669 raise
670
671
672@@ -871,15 +678,16 @@
673 if volid:
674 if volume_is_permanent(volid):
675 if not volume_init_and_mount(volid):
676- juju_log(MSG_ERROR,
677- "volume_init_and_mount failed, "
678- "not applying changes")
679+ log(
680+ "volume_init_and_mount failed, not applying changes",
681+ ERROR)
682 return False
683
684 if not os.path.exists(data_directory_path):
685- juju_log(MSG_CRITICAL,
686- "postgresql data dir = %s not found, "
687- "not applying changes." % data_directory_path)
688+ log(
689+ "postgresql data dir {} not found, "
690+ "not applying changes.".format(data_directory_path),
691+ CRITICAL)
692 return False
693
694 mount_point = volume_mount_point_from_volid(volid)
695@@ -887,22 +695,22 @@
696 new_pg_version_cluster_dir = os.path.join(
697 new_pg_dir, config_data["version"], config_data["cluster_name"])
698 if not mount_point:
699- juju_log(MSG_ERROR,
700- "invalid mount point from volid = \"%s\", "
701- "not applying changes." % mount_point)
702+ log(
703+ "invalid mount point from volid = {}, "
704+ "not applying changes.".format(mount_point), ERROR)
705 return False
706
707 if ((os.path.islink(data_directory_path) and
708 os.readlink(data_directory_path) == new_pg_version_cluster_dir and
709 os.path.isdir(new_pg_version_cluster_dir))):
710- juju_log(MSG_INFO,
711- "NOTICE: postgresql data dir '%s' already points "
712- "to '%s', skipping storage changes." %
713- (data_directory_path, new_pg_version_cluster_dir))
714- juju_log(MSG_INFO,
715- "existing-symlink: to fix/avoid UID changes from "
716- "previous units, doing: "
717- "chown -R postgres:postgres %s" % new_pg_dir)
718+ log(
719+ "postgresql data dir '%s' already points "
720+ "to {}, skipping storage changes.".format(
721+ data_directory_path, new_pg_version_cluster_dir))
722+ log(
723+ "existing-symlink: to fix/avoid UID changes from "
724+ "previous units, doing: "
725+ "chown -R postgres:postgres {}".format(new_pg_dir))
726 run("chown -R postgres:postgres %s" % new_pg_dir)
727 return True
728
729@@ -914,7 +722,7 @@
730 os.path.join(new_pg_dir, config_data["version"]),
731 new_pg_version_cluster_dir]:
732 if not os.path.isdir(new_dir):
733- juju_log(MSG_INFO, "mkdir %s" % new_dir)
734+ log("mkdir %s".format(new_dir))
735 os.mkdir(new_dir)
736 # copy permissions from current data_directory_path
737 os.chown(new_dir, curr_dir_stat.st_uid, curr_dir_stat.st_gid)
738@@ -925,45 +733,49 @@
739 # but keep previous "main/" directory, by renaming it to
740 # main-$TIMESTAMP
741 if not postgresql_stop():
742- juju_log(MSG_ERROR,
743- "postgresql_stop() returned False - can't migrate data.")
744+ log("postgresql_stop() failed - can't migrate data.", ERROR)
745 return False
746- if not os.path.exists(os.path.join(new_pg_version_cluster_dir,
747- "PG_VERSION")):
748- juju_log(MSG_WARNING, "migrating PG data %s/ -> %s/" % (
749- data_directory_path, new_pg_version_cluster_dir))
750+ if not os.path.exists(os.path.join(
751+ new_pg_version_cluster_dir, "PG_VERSION")):
752+ log("migrating PG data {}/ -> {}/".format(
753+ data_directory_path, new_pg_version_cluster_dir), WARNING)
754 # void copying PID file to perm storage (shouldn't be any...)
755- command = "rsync -a --exclude postmaster.pid %s/ %s/" % \
756- (data_directory_path, new_pg_version_cluster_dir)
757- juju_log(MSG_INFO, "run: %s" % command)
758- #output = run(command)
759+ command = "rsync -a --exclude postmaster.pid {}/ {}/".format(
760+ data_directory_path, new_pg_version_cluster_dir)
761+ log("run: {}".format(command))
762 run(command)
763 try:
764- os.rename(data_directory_path, "%s-%d" % (
765+ os.rename(data_directory_path, "{}-{}".format(
766 data_directory_path, int(time.time())))
767- juju_log(MSG_INFO, "NOTICE: symlinking %s -> %s" %
768- (new_pg_version_cluster_dir, data_directory_path))
769+ log("NOTICE: symlinking {} -> {}".format(
770+ new_pg_version_cluster_dir, data_directory_path))
771 os.symlink(new_pg_version_cluster_dir, data_directory_path)
772- juju_log(MSG_INFO,
773- "after-symlink: to fix/avoid UID changes from "
774- "previous units, doing: "
775- "chown -R postgres:postgres %s" % new_pg_dir)
776- run("chown -R postgres:postgres %s" % new_pg_dir)
777+ log(
778+ "after-symlink: to fix/avoid UID changes from "
779+ "previous units, doing: "
780+ "chown -R postgres:postgres {}".format(new_pg_dir))
781+ run("chown -R postgres:postgres {}".format(new_pg_dir))
782 return True
783 except OSError:
784- juju_log(MSG_CRITICAL, "failed to symlink \"%s\" -> \"%s\"" % (
785- data_directory_path, mount_point))
786+ log("failed to symlink {} -> {}".format(
787+ data_directory_path, mount_point), CRITICAL)
788 return False
789 else:
790- juju_log(MSG_ERROR, "ERROR: Invalid volume storage configuration, " +
791- "not applying changes")
792+ log(
793+ "Invalid volume storage configuration, not applying changes",
794+ ERROR)
795 return False
796
797
798-###############################################################################
799-# Hook functions
800-###############################################################################
801-def config_changed(postgresql_config, force_restart=False):
802+def token_sql_safe(value):
803+ # Only allow alphanumeric + underscore in database identifiers
804+ if re.search('[^A-Za-z0-9_]', value):
805+ return False
806+ return True
807+
808+
809+@hooks.hook()
810+def config_changed(force_restart=False):
811
812 add_extra_repos()
813
814@@ -975,11 +787,11 @@
815 postgresql_stop()
816 mounts = volume_get_all_mounted()
817 if mounts:
818- juju_log(MSG_INFO, "FYI current mounted volumes: %s" % mounts)
819- juju_log(MSG_ERROR,
820- "Disabled and stopped postgresql service, "
821- "because of broken volume configuration - check "
822- "'volume-ephemeral-storage' and 'volume-map'")
823+ log("current mounted volumes: {}".format(mounts))
824+ log(
825+ "Disabled and stopped postgresql service, "
826+ "because of broken volume configuration - check "
827+ "'volume-ephemeral-storage' and 'volume-map'", ERROR)
828 sys.exit(1)
829
830 if volume_is_permanent(volid):
831@@ -992,10 +804,10 @@
832 postgresql_stop()
833 mounts = volume_get_all_mounted()
834 if mounts:
835- juju_log(MSG_INFO, "FYI current mounted volumes: %s" % mounts)
836- juju_log(MSG_ERROR,
837- "Disabled and stopped postgresql service "
838- "(config_changed_volume_apply failure)")
839+ log("current mounted volumes: {}".format(mounts))
840+ log(
841+ "Disabled and stopped postgresql service "
842+ "(config_changed_volume_apply failure)", ERROR)
843 sys.exit(1)
844 current_service_port = get_service_port(postgresql_config)
845 create_postgresql_config(postgresql_config)
846@@ -1010,13 +822,7 @@
847 return postgresql_reload_or_restart()
848
849
850-def token_sql_safe(value):
851- # Only allow alphanumeric + underscore in database identifiers
852- if re.search('[^A-Za-z0-9_]', value):
853- return False
854- return True
855-
856-
857+@hooks.hook()
858 def install(run_pre=True):
859 if run_pre:
860 for f in glob.glob('exec.d/*/charm-pre-install'):
861@@ -1028,8 +834,9 @@
862 packages = ["postgresql", "pwgen", "python-jinja2", "syslinux",
863 "python-psycopg2", "postgresql-contrib", "postgresql-plpython",
864 "postgresql-%s-debversion" % config_data["version"]]
865- packages.extend(config_data["extra-packages"].split())
866- apt_get_install(packages)
867+ packages.extend((hookenv.config('extra-packages') or '').split())
868+ packages = host.filter_installed_packages(packages)
869+ host.apt_install(packages, fatal=True)
870
871 if not 'state' in local_state:
872 # Fresh installation. Because this function is invoked by both
873@@ -1045,9 +852,9 @@
874 run("pg_createcluster --locale='{}' --encoding='{}' 9.1 main".format(
875 config_data['locale'], config_data['encoding']))
876
877- install_dir(postgresql_backups_dir, owner="postgres", mode=0755)
878- install_dir(postgresql_scripts_dir, owner="postgres", mode=0755)
879- install_dir(postgresql_logs_dir, owner="postgres", mode=0755)
880+ host.mkdir(postgresql_backups_dir, owner="postgres", perms=0o755)
881+ host.mkdir(postgresql_scripts_dir, owner="postgres", perms=0o755)
882+ host.mkdir(postgresql_logs_dir, owner="postgres", perms=0o755)
883 paths = {
884 'base_dir': postgresql_data_dir,
885 'backup_dir': postgresql_backups_dir,
886@@ -1058,25 +865,41 @@
887 open("templates/dump-pg-db.tmpl").read()).render(paths)
888 backup_job = Template(
889 open("templates/pg_backup_job.tmpl").read()).render(paths)
890- install_file(dump_script, '{}/dump-pg-db'.format(postgresql_scripts_dir),
891- mode=0755)
892- install_file(backup_job, '{}/pg_backup_job'.format(postgresql_scripts_dir),
893- mode=0755)
894+ write_file(
895+ '{}/dump-pg-db'.format(postgresql_scripts_dir),
896+ dump_script, perms=0755)
897+ write_file(
898+ '{}/pg_backup_job'.format(postgresql_scripts_dir),
899+ backup_job, perms=0755)
900 install_postgresql_crontab(postgresql_crontab)
901- open_port(5432)
902+ hookenv.open_port(5432)
903
904 # Ensure at least minimal access granted for hooks to run.
905 # Reload because we are using the default cluster setup and started
906 # when we installed the PostgreSQL packages.
907- config_changed(postgresql_config, force_restart=True)
908+ config_changed(force_restart=True)
909
910 snapshot_relations()
911
912
913+@hooks.hook()
914 def upgrade_charm():
915+ install(run_pre=False)
916 snapshot_relations()
917
918
919+@hooks.hook()
920+def start():
921+ if not postgresql_restart():
922+ raise SystemExit(1)
923+
924+
925+@hooks.hook()
926+def stop():
927+ if not postgresql_stop():
928+ raise SystemExit(1)
929+
930+
931 def quote_identifier(identifier):
932 r'''Quote an identifier, such as a table or role name.
933
934@@ -1220,20 +1043,6 @@
935 AsIs(quote_identifier(user)))
936
937
938-def get_relation_host():
939- remote_host = run("relation-get ip")
940- if not remote_host:
941- # remote unit $JUJU_REMOTE_UNIT uses deprecated 'ip=' component of
942- # interface.
943- remote_host = run("relation-get private-address")
944- return remote_host
945-
946-
947-def get_unit_host():
948- this_host = run("unit-get private-address")
949- return this_host.strip()
950-
951-
952 def snapshot_relations():
953 '''Snapshot our relation information into local state.
954
955@@ -1299,10 +1108,24 @@
956 # slave replication-relation-changed (noop; slave not yet joined db rel)
957 # slave db-relation-joined (republish)
958
959-def db_relation_joined_changed(user, database, roles):
960- if local_state['state'] not in ('master', 'standalone'):
961+@hooks.hook('db-relation-joined', 'db-relation-changed')
962+def db_relation_joined_changed():
963+ if local_state['state'] == 'hot standby':
964+ publish_hot_standby_credentials()
965 return
966
967+ # By default, we create a database named after the remote
968+ # servicename. The remote service can override this by setting
969+ # the database property on the relation.
970+ database = hookenv.relation_get('database')
971+ if not database:
972+ database = hookenv.remote_unit().split('/')[0]
973+
974+ # Generate a unique username for this relation to use.
975+ user = user_name(hookenv.relation_id(), hookenv.remote_unit())
976+
977+ roles = filter(None, (hookenv.relation_get('roles') or '').split(","))
978+
979 log('{} unit publishing credentials'.format(local_state['state']))
980
981 password = create_user(user)
982@@ -1310,8 +1133,8 @@
983 schema_user = "{}_schema".format(user)
984 schema_password = create_user(schema_user)
985 ensure_database(user, schema_user, database)
986- host = get_unit_host()
987- port = config_get()["listen_port"]
988+ host = hookenv.unit_private_ip()
989+ port = hookenv.config('listen_port')
990 state = local_state['state'] # master, hot standby, standalone
991
992 # Publish connection details.
993@@ -1336,15 +1159,20 @@
994 snapshot_relations()
995
996
997-def db_admin_relation_joined_changed(user):
998- if local_state['state'] not in ('master', 'standalone'):
999+@hooks.hook('db-admin-relation-joined', 'db-admin-relation-changed')
1000+def db_admin_relation_joined_changed():
1001+ if local_state['state'] == 'hot standby':
1002+ publish_hot_standby_credentials()
1003 return
1004
1005+ user = user_name(
1006+ hookenv.relation_id(), hookenv.remote_unit(), admin=True)
1007+
1008 log('{} unit publishing credentials'.format(local_state['state']))
1009
1010 password = create_user(user, admin=True)
1011- host = get_unit_host()
1012- port = config_get()["listen_port"]
1013+ host = hookenv.unit_private_ip()
1014+ port = hookenv.config('listen_port')
1015 state = local_state['state'] # master, hot standby, standalone
1016
1017 # Publish connection details.
1018@@ -1366,6 +1194,7 @@
1019 snapshot_relations()
1020
1021
1022+@hooks.hook()
1023 def db_relation_broken():
1024 from psycopg2.extensions import AsIs
1025
1026@@ -1398,6 +1227,7 @@
1027 snapshot_relations()
1028
1029
1030+@hooks.hook()
1031 def db_admin_relation_broken():
1032 from psycopg2.extensions import AsIs
1033
1034@@ -1413,12 +1243,8 @@
1035 snapshot_relations()
1036
1037
1038-def TODO(msg):
1039- juju_log(MSG_WARNING, 'TODO> %s' % msg)
1040-
1041-
1042 def add_extra_repos():
1043- extra_repos = config_get('extra_archives')
1044+ extra_repos = hookenv.config('extra_archives')
1045 extra_repos_added = local_state.setdefault('extra_repos_added', set())
1046 if extra_repos:
1047 repos_added = False
1048@@ -1428,7 +1254,7 @@
1049 extra_repos_added.add(repo)
1050 repos_added = True
1051 if repos_added:
1052- run('apt-get update')
1053+ host.apt_update(fatal=True)
1054 local_state.save()
1055
1056
1057@@ -1441,7 +1267,7 @@
1058 """
1059 comment = 'repmgr key for {}'.format(os.environ['JUJU_UNIT_NAME'])
1060 if not os.path.isdir(postgres_ssh_dir):
1061- install_dir(postgres_ssh_dir, "postgres", "postgres", 0700)
1062+ host.mkdir(postgres_ssh_dir, "postgres", "postgres", 0o700)
1063 if not os.path.exists(postgres_ssh_private_key):
1064 run("sudo -u postgres -H ssh-keygen -q -t rsa -C '{}' -N '' "
1065 "-f '{}'".format(comment, postgres_ssh_private_key))
1066@@ -1457,9 +1283,9 @@
1067 authorized_units = set()
1068 authorized_keys = set()
1069 known_hosts = set()
1070- for relid in relation_ids(relation_types=replication_relation_types):
1071- for unit in relation_list(relid):
1072- relation = relation_get(unit_name=unit, relation_id=relid)
1073+ for relid in hookenv.relation_ids('replication'):
1074+ for unit in hookenv.related_units(relid):
1075+ relation = hookenv.relation_get(unit=unit, rid=relid)
1076 public_key = relation.get('public_ssh_key', None)
1077 if public_key:
1078 authorized_units.add(unit)
1079@@ -1468,14 +1294,14 @@
1080 relation['private-address'], relation['ssh_host_key']))
1081
1082 # Generate known_hosts
1083- install_file(
1084- '\n'.join(known_hosts), postgres_ssh_known_hosts,
1085- owner="postgres", group="postgres", mode=0o644)
1086+ write_file(
1087+ postgres_ssh_known_hosts, '\n'.join(known_hosts),
1088+ owner="postgres", group="postgres", perms=0o644)
1089
1090 # Generate authorized_keys
1091- install_file(
1092- '\n'.join(authorized_keys), postgres_ssh_authorized_keys,
1093- owner="postgres", group="postgres", mode=0o400)
1094+ write_file(
1095+ postgres_ssh_authorized_keys, '\n'.join(authorized_keys),
1096+ owner="postgres", group="postgres", perms=0o400)
1097
1098 # Publish details, so relation knows they have been granted access.
1099 local_state['authorized'] = authorized_units
1100@@ -1495,9 +1321,9 @@
1101 pgpass = '\n'.join(
1102 "*:*:*:{}:{}".format(username, password)
1103 for username, password in passwords.items())
1104- install_file(
1105- pgpass, charm_pgpass,
1106- owner="postgres", group="postgres", mode=0o400)
1107+ write_file(
1108+ charm_pgpass, pgpass,
1109+ owner="postgres", group="postgres", perms=0o400)
1110
1111
1112 def drop_database(dbname, warn=True):
1113@@ -1511,8 +1337,7 @@
1114 except psycopg2.Error:
1115 if time.time() > now + timeout:
1116 if warn:
1117- juju_log(
1118- MSG_WARNING, "Unable to drop database %s" % dbname)
1119+ log("Unable to drop database {}".format(dbname), WARNING)
1120 else:
1121 raise
1122 time.sleep(0.5)
1123@@ -1542,26 +1367,9 @@
1124 def follow_database(master):
1125 '''Connect the database as a streaming replica of the master.'''
1126 master_relation = hookenv.relation_get(unit=master)
1127-
1128- recovery_conf_path = os.path.join(postgresql_cluster_dir, 'recovery.conf')
1129- if os.path.exists(recovery_conf_path):
1130- old_recovery_conf = open(recovery_conf_path, 'r').read()
1131- else:
1132- old_recovery_conf = None
1133-
1134- recovery_conf = Template(
1135- open("templates/recovery.conf.tmpl").read()).render({
1136- 'host': master_relation['private-address'],
1137- 'password': local_state['replication_password']})
1138- juju_log(MSG_DEBUG, recovery_conf)
1139- install_file(
1140- recovery_conf,
1141- os.path.join(postgresql_cluster_dir, 'recovery.conf'),
1142- owner="postgres", group="postgres")
1143-
1144- if recovery_conf != old_recovery_conf:
1145- log("recovery.conf updated. Restarting to take effect.")
1146- postgresql_restart()
1147+ create_recovery_conf(
1148+ master_relation['private-address'],
1149+ local_state['replication_password'], restart_on_change=True)
1150
1151
1152 def elected_master():
1153@@ -1631,8 +1439,9 @@
1154 return master
1155
1156
1157+@hooks.hook('replication-relation-joined', 'replication-relation-changed')
1158 def replication_relation_joined_changed():
1159- config_changed(postgresql_config) # Ensure minimal replication settings.
1160+ config_changed() # Ensure minimal replication settings.
1161
1162 # Now that pg_hba.conf has been regenerated and loaded, inform related
1163 # units that they have been granted replication access.
1164@@ -1724,11 +1533,11 @@
1165 def publish_hot_standby_credentials():
1166 '''
1167 If a hot standby joins a client relation before the master
1168- unit, it was unable to publish connection details. However,
1169+ unit, it is unable to publish connection details. However,
1170 when the master does join it updates the client_relations
1171- value in the peer relation causing the
1172- replication-relation-changed hook to be invoked. This gives us
1173- a second opertunity to publish connection details.
1174+ value in the peer relation causing the replication-relation-changed
1175+ hook to be invoked. This gives us a second opertunity to publish
1176+ connection details.
1177
1178 This function is invoked from both the client and peer
1179 relation-changed hook. One of these will work depending on the order
1180@@ -1737,7 +1546,7 @@
1181 master = local_state['following']
1182
1183 client_relations = hookenv.relation_get(
1184- 'client_relations', master, relation_ids('replication')[0])
1185+ 'client_relations', master, hookenv.relation_ids('replication')[0])
1186
1187 if client_relations is None:
1188 log("Master {} has not yet joined any client relations".format(
1189@@ -1763,8 +1572,8 @@
1190 unit=master, rid=client_relation)
1191
1192 # Override unit specific connection details
1193- connection_settings['host'] = get_unit_host()
1194- connection_settings['port'] = config_get()["listen_port"]
1195+ connection_settings['host'] = hookenv.unit_private_ip()
1196+ connection_settings['port'] = hookenv.config('listen_port')
1197 connection_settings['state'] = local_state['state']
1198
1199 # Block until users and database has replicated, so we know the
1200@@ -1787,11 +1596,10 @@
1201 client_relation, relation_settings=connection_settings)
1202
1203
1204+@hooks.hook()
1205 def replication_relation_departed():
1206 '''A unit has left the replication peer group.'''
1207 remote_unit = hookenv.remote_unit()
1208- remote_relation = hookenv.relation_get()
1209- remote_state = remote_relation['state']
1210
1211 assert remote_unit is not None
1212
1213@@ -1839,32 +1647,33 @@
1214 if 'paused_at_failover' in local_state:
1215 del local_state['paused_at_failover']
1216
1217- config_changed(postgresql_config)
1218+ config_changed()
1219 local_state.publish()
1220
1221
1222+@hooks.hook()
1223 def replication_relation_broken():
1224 # This unit has been removed from the service.
1225 promote_database()
1226 if os.path.exists(charm_pgpass):
1227 os.unlink(charm_pgpass)
1228- config_changed(postgresql_config)
1229+ config_changed()
1230
1231
1232 def clone_database(master_unit, master_host):
1233 postgresql_stop()
1234- juju_log(MSG_INFO, "Cloning master {}".format(master_unit))
1235+ log("Cloning master {}".format(master_unit))
1236
1237 cmd = ['sudo', '-E', '-u', 'postgres', # -E needed to locate pgpass file.
1238 'pg_basebackup', '-D', postgresql_cluster_dir,
1239 '--xlog', '--checkpoint=fast', '--no-password',
1240 '-h', master_host, '-p', '5432', '--username=juju_replication']
1241- juju_log(MSG_DEBUG, ' '.join(cmd))
1242+ log(' '.join(cmd), DEBUG)
1243 if os.path.isdir(postgresql_cluster_dir):
1244 shutil.rmtree(postgresql_cluster_dir)
1245 try:
1246 output = subprocess.check_output(cmd)
1247- juju_log(MSG_DEBUG, output)
1248+ log(output, DEBUG)
1249 # Debian by default expects SSL certificates in the datadir.
1250 os.symlink(
1251 '/etc/ssl/certs/ssl-cert-snakeoil.pem',
1252@@ -1872,29 +1681,21 @@
1253 os.symlink(
1254 '/etc/ssl/private/ssl-cert-snakeoil.key',
1255 os.path.join(postgresql_cluster_dir, 'server.key'))
1256- recovery_conf = Template(
1257- open("templates/recovery.conf.tmpl").read()).render({
1258- 'host': master_host,
1259- 'password': local_state['replication_password']})
1260- juju_log(MSG_DEBUG, recovery_conf)
1261- install_file(
1262- recovery_conf,
1263- os.path.join(postgresql_cluster_dir, 'recovery.conf'),
1264- owner="postgres", group="postgres")
1265+ create_recovery_conf(master_host, local_state['replication_password'])
1266 except subprocess.CalledProcessError, x:
1267 # We failed, and this cluster is broken. Rebuild a
1268 # working cluster so start/stop etc. works and we
1269 # can retry hooks again. Even assuming the charm is
1270 # functioning correctly, the clone may still fail
1271 # due to eg. lack of disk space.
1272- juju_log(MSG_ERROR, "Clone failed, db cluster destroyed")
1273- juju_log(MSG_ERROR, x.output)
1274+ log("Clone failed, db cluster destroyed", ERROR)
1275+ log(x.output, ERROR)
1276 if os.path.exists(postgresql_cluster_dir):
1277 shutil.rmtree(postgresql_cluster_dir)
1278 if os.path.exists(postgresql_config_dir):
1279 shutil.rmtree(postgresql_config_dir)
1280 run('pg_createcluster {} main'.format(version))
1281- config_changed(postgresql_config)
1282+ config_changed()
1283 raise
1284 finally:
1285 postgresql_start()
1286@@ -1903,8 +1704,8 @@
1287
1288 def slave_count():
1289 num_slaves = 0
1290- for relid in relation_ids(relation_types=replication_relation_types):
1291- num_slaves += len(relation_list(relid))
1292+ for relid in hookenv.relation_ids('replication'):
1293+ num_slaves += len(hookenv.related_units(relid))
1294 return num_slaves
1295
1296
1297@@ -1949,8 +1750,9 @@
1298 units, lambda a, b: cmp(int(a.split('/')[-1]), int(b.split('/')[-1])))
1299
1300
1301+@hooks.hook('nrpe-external-master-relation-changed')
1302 def update_nrpe_checks():
1303- config_data = config_get()
1304+ config_data = hookenv.config()
1305 try:
1306 nagios_uid = getpwnam('nagios').pw_uid
1307 nagios_gid = getgrnam('nagios').gr_gid
1308@@ -1958,7 +1760,7 @@
1309 hookenv.log("Nagios user not set up.", hookenv.DEBUG)
1310 return
1311
1312- unit_name = os.environ['JUJU_UNIT_NAME'].replace('/', '-')
1313+ unit_name = hookenv.local_unit().replace('/', '-')
1314 nagios_hostname = "%s-%s" % (config_data['nagios_context'], unit_name)
1315 nagios_logdir = '/var/log/nagios'
1316 nrpe_service_file = \
1317@@ -2009,10 +1811,11 @@
1318 if os.path.isfile('/etc/init.d/nagios-nrpe-server'):
1319 subprocess.call(['service', 'nagios-nrpe-server', 'reload'])
1320
1321+
1322 ###############################################################################
1323 # Global variables
1324 ###############################################################################
1325-config_data = config_get()
1326+config_data = hookenv.config()
1327 version = config_data['version']
1328 cluster_name = config_data['cluster_name']
1329 postgresql_data_dir = "/var/lib/postgresql"
1330@@ -2046,10 +1849,7 @@
1331 os.environ['PGPASSFILE'] = charm_pgpass
1332
1333
1334-###############################################################################
1335-# Main section
1336-###############################################################################
1337-def main():
1338+if __name__ == '__main__':
1339 # Hook and context overview. The various replication and client
1340 # hooks interact in complex ways.
1341 log("Running {} hook".format(hook_name))
1342@@ -2057,90 +1857,4 @@
1343 log("Relation {} with {}".format(
1344 hookenv.relation_id(), hookenv.remote_unit()))
1345
1346- if hook_name == "install":
1347- install()
1348-
1349- elif hook_name == "config-changed":
1350- config_changed(postgresql_config)
1351-
1352- elif hook_name == "upgrade-charm":
1353- install(run_pre=False)
1354- upgrade_charm()
1355-
1356- elif hook_name == "start":
1357- if not postgresql_restart():
1358- raise SystemExit(1)
1359-
1360- elif hook_name == "stop":
1361- if not postgresql_stop():
1362- raise SystemExit(1)
1363-
1364- elif hook_name == "db-relation-joined":
1365- # By default, we create a database named after the remote
1366- # servicename. The remote service can override this by setting
1367- # the database property on the relation.
1368- database = os.environ['JUJU_REMOTE_UNIT'].split('/')[0]
1369-
1370- # Generate a unique username for this relation to use.
1371- user = user_name(
1372- os.environ['JUJU_RELATION_ID'], os.environ['JUJU_REMOTE_UNIT'])
1373-
1374- db_relation_joined_changed(user, database, []) # No roles yet.
1375-
1376- elif hook_name == "db-relation-changed":
1377- roles = filter(None, (relation_get('roles') or '').split(","))
1378-
1379- # If the remote service has requested we use a particular database
1380- # name, honour that request.
1381- database = relation_get('database')
1382- if not database:
1383- database = relation_get('database', os.environ['JUJU_UNIT_NAME'])
1384-
1385- user = relation_get('user', os.environ['JUJU_UNIT_NAME'])
1386- if not user:
1387- user = user_name(
1388- os.environ['JUJU_RELATION_ID'], os.environ['JUJU_REMOTE_UNIT'])
1389- db_relation_joined_changed(user, database, roles)
1390-
1391- elif hook_name == "db-relation-broken":
1392- db_relation_broken()
1393-
1394- elif hook_name in ("db-admin-relation-joined",
1395- "db-admin-relation-changed"):
1396- user = user_name(os.environ['JUJU_RELATION_ID'],
1397- os.environ['JUJU_REMOTE_UNIT'], admin=True)
1398- db_admin_relation_joined_changed(user)
1399-
1400- elif hook_name == "db-admin-relation-broken":
1401- db_admin_relation_broken()
1402-
1403- elif hook_name == "nrpe-external-master-relation-changed":
1404- update_nrpe_checks()
1405-
1406- elif hook_name == 'replication-relation-joined':
1407- replication_relation_joined_changed()
1408-
1409- elif hook_name == 'replication-relation-changed':
1410- replication_relation_joined_changed()
1411-
1412- elif hook_name == 'replication-relation-departed':
1413- replication_relation_departed()
1414-
1415- elif hook_name == 'replication-relation-broken':
1416- replication_relation_broken()
1417-
1418- #-------- persistent-storage-relation-joined,
1419- # persistent-storage-relation-changed
1420- #elif hook_name in ["persistent-storage-relation-joined",
1421- # "persistent-storage-relation-changed"]:
1422- # persistent_storage_relation_joined_changed()
1423- #-------- persistent-storage-relation-broken
1424- #elif hook_name == "persistent-storage-relation-broken":
1425- # persistent_storage_relation_broken()
1426- else:
1427- print "Unknown hook {}".format(hook_name)
1428- raise SystemExit(1)
1429-
1430-
1431-if __name__ == '__main__':
1432- raise SystemExit(main())
1433+ hooks.execute(sys.argv)
1434
1435=== modified file 'test.py'
1436--- test.py 2013-07-08 11:07:29 +0000
1437+++ test.py 2013-07-08 11:07:29 +0000
1438@@ -255,7 +255,7 @@
1439 _run(self, cmd)
1440
1441 def test_basic(self):
1442- '''Set up a single unit service'''
1443+ '''Connect to a a single unit service via the db relationship.'''
1444 self.juju.deploy(TEST_CHARM, 'postgresql')
1445 self.juju.deploy(PSQL_CHARM, 'psql')
1446 self.juju.do(['add-relation', 'postgresql:db', 'psql:db'])
1447@@ -265,10 +265,20 @@
1448 # from adding the relation. I'm protected here as 'juju status'
1449 # takes about 25 seconds to run from here to my test cloud but
1450 # others might not be so 'lucky'.
1451- self.addDetail('status', text_content(repr(self.juju.status)))
1452 result = self.sql('SELECT TRUE')
1453 self.assertEqual(result, [['t']])
1454
1455+ def test_basic_admin(self):
1456+ '''Connect to a single unit service via the db-admin relationship.'''
1457+ self.juju.deploy(TEST_CHARM, 'postgresql')
1458+ self.juju.deploy(PSQL_CHARM, 'psql')
1459+ self.juju.do(['add-relation', 'postgresql:db-admin', 'psql:db-admin'])
1460+ self.juju.wait_until_ready()
1461+
1462+ result = self.sql('SELECT TRUE', dbname='postgres')
1463+ self.assertEqual(result, [['t']])
1464+
1465+
1466 def is_master(self, postgres_unit, dbname=None):
1467 is_master = self.sql(
1468 'SELECT NOT pg_is_in_recovery()',

Subscribers

People subscribed via source and target branches