Merge lp:~stub/charms/precise/postgresql/cleanups into lp:charms/postgresql

Proposed by Stuart Bishop
Status: Merged
Merged at revision: 85
Proposed branch: lp:~stub/charms/precise/postgresql/cleanups
Merge into: lp:charms/postgresql
Prerequisite: lp:~stub/charms/precise/postgresql/charm-helpers
Diff against target: 154 lines (+85/-27)
2 files modified
README.md (+68/-1)
hooks/hooks.py (+17/-26)
To merge this branch: bzr merge lp:~stub/charms/precise/postgresql/cleanups
Reviewer Review Type Date Requested Status
Marco Ceppi (community) Approve
Charles Butler (community) Approve
Review via email: mp+203701@code.launchpad.net

Description of the change

Remove an unused helper and fix some edge cases in the relation-broken hooks.

To post a comment you must log in.
Revision history for this message
Charles Butler (lazypower) wrote :

Greetings Stuart,

I'm reviewing the merge details and from what I understand is going on here, you're handling some cases where access to the data would happen due to another hook stripping access from the proposed user correct?

I've deployed this branch and poked about in postgres. The block was reached and defaulted away from removing access from the user, which was nice to see that its idempotent in caring about data access and integrity after the relationship departs.

You get my +1, and if I was off base in the review please correct me.

Thanks for your continued effort on maintaining a high quality charm. Another member of the charmers team will be along shortly to continue the review and potentially promulgate your changes.

review: Approve
105. By Stuart Bishop

Merged charm-helpers into cleanups.

106. By Stuart Bishop

Require clients to wait for database to acknowledge requested database name, and add example client relationship hooks to demonstrate the protocol more clearly

107. By Stuart Bishop

Fix bug in example hooks (relation_get() may return None)

Revision history for this message
Stuart Bishop (stub) wrote :

@lazypower Almost, yes. I'm handling the cases where the user or database was blown away completely. This could happen by an administrator doing it manually, or pathological situations in the hooks. For the insanely curious, I can't quite remember the pathalogical situation now, but I think it was something like adding a client relation to a replicated PostgreSQL service and then immediately destroying the PostgreSQL service. A hot standbys could have ended up as a standalone database before the created users and databases had replicated across, and then attempting to remove privileges from objects that never got around to existing. The sort of error only the test suite will ever see.

I managed to pump new documentation into README.md after Charles' review sorry. I can move it elsewhere if it is a problem.

Revision history for this message
Marco Ceppi (marcoceppi) wrote :

More READMEs are not a problem at all! We appreciate the effort completely!

Revision history for this message
Marco Ceppi (marcoceppi) wrote :

LGTM +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README.md'
2--- README.md 2014-01-15 10:28:22 +0000
3+++ README.md 2014-02-05 12:31:00 +0000
4@@ -94,7 +94,9 @@
5 ### the client service provides:
6
7 - `database`: Optional. The name of the database to use. The postgresql service
8- will create it if necessary.
9+ will create it if necessary. If your charm sets this, then it must wait
10+ until a matching `database` value is presented on the PostgreSQL side of
11+ the relation (ie. `relation-get database` returns the value you set).
12 - `roles`: Optional. A comma separated list of database roles to grant the
13 database user. Typically these roles will have been granted permissions to
14 access the tables and other database objects. Do not grant permissions
15@@ -154,6 +156,71 @@
16 even having the service revert to a single 'standalone' unit.
17
18
19+## Example client hooks
20+
21+Python::
22+
23+ import sys
24+ from charmhelpers.core.hookenv import (
25+ Hooks, config, relation_set, relation_get,
26+ local_unit, related_units, remote_unit)
27+
28+ hooks = Hooks()
29+ hook = hooks.hook
30+
31+ @hook
32+ def db_relation_joined():
33+ relation_set('database', config('database')) # Explicit database name
34+ relation_set('roles', 'reporting,standard') # DB roles required
35+
36+ @hook
37+ def db_relation_changed():
38+ db_changed(related_units())
39+
40+ @hook
41+ def db_relation_departed():
42+ # Note we currently have to explicitly filter the dying unit
43+ # from our list of database units due to LP Bug #1192433
44+ db_changed(
45+ unit for unit in related_units() if unit != remote_unit())
46+
47+ @hook
48+ def db_relation_broken():
49+ db_changed([])
50+
51+ def db_changed(active_db_units):
52+ # Rather than try to merge in just this particular database
53+ # connection that triggered the hook into our existing connections,
54+ # it is easier to iterate over all active related databases and
55+ # reset the entire list of connections.
56+ conn_str_tmpl = "dbname={dbname} user={user} host={host} port={port}"
57+ master_conn_str = None
58+ slave_conn_strs = []
59+ for db_unit in active_db_units:
60+ if relation_get('database', db_unit) != config('database'):
61+ continue # Not yet acknowledged requested database name.
62+
63+ allowed_units = relation_get('allowed-units') or '' # May be None
64+ if local_unit() not in allowed_units.split():
65+ continue # Not yet authorized.
66+
67+ conn_str = conn_str_tmpl.format(**relation_get(unit=db_unit)
68+ remote_state = relation_get('state', db_unit)
69+
70+ if remote_state == 'standalone' and len(active_db_units) == 1:
71+ master_conn_str = conn_str
72+ elif relation_state == 'master':
73+ master_conn_str = conn_str
74+ elif relation_state == 'hot standby':
75+ slave_conn_strs.append(conn_str)
76+
77+ update_my_db_config(master=master_conn_str, slaves=slave_conn_strs)
78+
79+ if __name__ == '__main__':
80+ hooks.execute(sys.argv)
81+
82+
83+
84 # Contact Information
85
86 ## PostgreSQL
87
88=== modified file 'hooks/hooks.py'
89--- hooks/hooks.py 2014-02-03 20:56:40 +0000
90+++ hooks/hooks.py 2014-02-05 12:31:00 +0000
91@@ -1345,11 +1345,16 @@
92 user = unit_relation_data.get('user', None)
93 database = unit_relation_data['database']
94
95- sql = "REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s"
96- run_sql_as_postgres(sql, AsIs(quote_identifier(database)),
97- AsIs(quote_identifier(user)))
98- run_sql_as_postgres(sql, AsIs(quote_identifier(database)),
99- AsIs(quote_identifier(user + "_schema")))
100+ # We need to check that the database still exists before
101+ # attempting to revoke privileges because the local PostgreSQL
102+ # cluster may have been rebuilt by another hook.
103+ sql = "SELECT datname FROM pg_database WHERE datname = %s"
104+ if run_select_as_postgres(sql, database)[0] != 0:
105+ sql = "REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s"
106+ run_sql_as_postgres(sql, AsIs(quote_identifier(database)),
107+ AsIs(quote_identifier(user)))
108+ run_sql_as_postgres(sql, AsIs(quote_identifier(database)),
109+ AsIs(quote_identifier(user + "_schema")))
110
111 postgresql_hba = os.path.join(_get_postgresql_config_dir(), "pg_hba.conf")
112 generate_postgresql_hba(postgresql_hba)
113@@ -1365,8 +1370,13 @@
114 if local_state['state'] in ('master', 'standalone'):
115 user = hookenv.relation_get('user', unit=hookenv.local_unit())
116 if user:
117- sql = "ALTER USER %s NOSUPERUSER"
118- run_sql_as_postgres(sql, AsIs(quote_identifier(user)))
119+ # We need to check that the user still exists before
120+ # attempting to revoke privileges because the local PostgreSQL
121+ # cluster may have been rebuilt by another hook.
122+ sql = "SELECT usename FROM pg_user WHERE usename = %s"
123+ if run_select_as_postgres(sql, user)[0] != 0:
124+ sql = "ALTER USER %s NOSUPERUSER"
125+ run_sql_as_postgres(sql, AsIs(quote_identifier(user)))
126
127 postgresql_hba = os.path.join(_get_postgresql_config_dir(), "pg_hba.conf")
128 generate_postgresql_hba(postgresql_hba)
129@@ -1434,25 +1444,6 @@
130 os.environ['PGPASSFILE'] = org_pgpassfile
131
132
133-def drop_database(dbname, warn=True):
134- import psycopg2
135- timeout = 120
136- now = time.time()
137- while True:
138- try:
139- db_cursor(autocommit=True).execute(
140- 'DROP DATABASE IF EXISTS "{}"'.format(dbname))
141- except psycopg2.Error:
142- if time.time() > now + timeout:
143- if warn:
144- log("Unable to drop database {}".format(dbname), WARNING)
145- else:
146- raise
147- time.sleep(0.5)
148- else:
149- break
150-
151-
152 def authorized_by(unit):
153 '''Return True if the peer has authorized our database connections.'''
154 relation = hookenv.relation_get(unit=unit)

Subscribers

People subscribed via source and target branches