Merge lp:~stub/charms/precise/postgresql/manual-replication into lp:charms/postgresql

Proposed by Stuart Bishop
Status: Merged
Merged at revision: 111
Proposed branch: lp:~stub/charms/precise/postgresql/manual-replication
Merge into: lp:charms/postgresql
Diff against target: 204 lines (+89/-19)
3 files modified
config.yaml (+16/-1)
hooks/hooks.py (+43/-13)
test.py (+30/-5)
To merge this branch: bzr merge lp:~stub/charms/precise/postgresql/manual-replication
Reviewer Review Type Date Requested Status
Review Queue (community) automated testing Needs Fixing
Charles Butler (community) Approve
Adam Israel (community) Approve
Review via email: mp+240556@code.launchpad.net

Description of the change

Allow replication to be handled manually, rather than Juju controlling it.

The main use of this is to allow replication to be setup between non-Juju PostgreSQL databases and charmed databases, such as the pending migration of the Launchpad database from its legacy servers to new MaaS and Juju controlled servers.

I also have a work in progress for cascading replication, where a PostgreSQL service can be configured to replicate to/from another PostgreSQL service. This too could be used to integrate non-Juju and charmed databases (or cross Juju environment), but it is a much more complex setup and lacks the flexibility of just allowing an experienced admin to dive in and handle it themselves.

To post a comment you must log in.
Revision history for this message
Adam Israel (aisrael) wrote :

Hey Stuart,

I had the opportunity to review this MP. The changes here look to be solid. Nice work, once again.

There is one minor lint error preventing me from marking this as approved, though. Once that is fixed, the test passes cleanly.

hooks/hooks.py:1148:9: E128 continuation line under-indented for visual indent

Thanks again for submitting this!

review: Needs Fixing
108. By Stuart Bishop

Merge trunk

109. By Stuart Bishop

delint and typo

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

Thanks. The lint leaked in from trunk. I've fixed it and corrected a minor spelling error in a comment.

Revision history for this message
Adam Israel (aisrael) wrote :

Thanks for the quick fix! I've tested and confirmed that the tests now pass cleanly. +1 from me.

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

+1

Thanks for the merge and diligence on getting this landed Stub. I'm approving and merging this - I left a note on IRC, and re-posting here for clarity.

<lazyPower> Looking over your merge against precise postgres charm - does this warrant additional action against the trusty charm as well when accepted for precise? https://code.launchpad.net/~stub/charms/precise/postgresql/manual-replication/+merge/240556
<lazyPower> or is this scoped strictly to precise

review: Approve
Revision history for this message
Review Queue (review-queue) wrote :

This items has failed automated testing! Results available here http://reports.vapour.ws/charm-tests/charm-bundle-test-10922-results

review: Needs Fixing (automated testing)
Revision history for this message
Review Queue (review-queue) wrote :

This items has failed automated testing! Results available here http://reports.vapour.ws/charm-tests/charm-bundle-test-10927-results

review: Needs Fixing (automated testing)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'config.yaml'
2--- config.yaml 2014-11-11 06:02:03 +0000
3+++ config.yaml 2014-12-04 17:50:16 +0000
4@@ -331,7 +331,22 @@
5 This will be written to the pg_hba.conf file, one line per rule.
6 Note that this should not be needed as db relations already create
7 those rules the right way. Only use this if you really need too
8- (e.g. on a development environment)
9+ (e.g. on a development environment), or are connecting juju managed
10+ databases to external managed systems, or configuring replication
11+ between unrelated PostgreSQL services using the manual_replication
12+ option.
13+ manual_replication:
14+ type: boolean
15+ default: False
16+ description: |
17+ Enable or disable charm managed replication. When manual_replication
18+ is True, the operator is responsible for maintaining recovery.conf
19+ and performing any necessary database mirroring. The charm will
20+ still advertise the unit as standalone, master or hot standby to
21+ relations based on whether the system is in recovery mode or not.
22+ Note that this option makes it possible to create a PostgreSQL
23+ service with multiple master units, which is probably a very silly
24+ thing to do.
25 backup_dir:
26 default: "/var/lib/postgresql/backups"
27 type: string
28
29=== modified file 'hooks/hooks.py'
30--- hooks/hooks.py 2014-11-11 06:02:03 +0000
31+++ hooks/hooks.py 2014-12-04 17:50:16 +0000
32@@ -646,6 +646,10 @@
33
34
35 def create_recovery_conf(master_host, master_port, restart_on_change=False):
36+ if hookenv.config('manual_replication'):
37+ log('manual_replication; should not be here', CRITICAL)
38+ raise RuntimeError('manual_replication; should not be here')
39+
40 version = pg_version()
41 cluster_name = hookenv.config('cluster_name')
42 postgresql_cluster_dir = os.path.join(
43@@ -1087,6 +1091,21 @@
44 return False
45
46
47+def reset_manual_replication_state():
48+ '''In manual replication mode, the state of the local database cluster
49+ is outside of Juju's control. We need to detect and update the charm
50+ state to match reality.
51+ '''
52+ if hookenv.config('manual_replication'):
53+ if os.path.exists('recovery.conf'):
54+ local_state['state'] = 'hot standby'
55+ elif slave_count():
56+ local_state['state'] = 'master'
57+ else:
58+ local_state['state'] = 'standalone'
59+ local_state.publish()
60+
61+
62 @hooks.hook()
63 def config_changed(force_restart=False, mount_point=None):
64 validate_config()
65@@ -1109,6 +1128,8 @@
66 "(config_changed_volume_apply failure)", ERROR)
67 sys.exit(1)
68
69+ reset_manual_replication_state()
70+
71 postgresql_config_dir = _get_postgresql_config_dir(config_data)
72 postgresql_config = os.path.join(postgresql_config_dir, "postgresql.conf")
73 postgresql_hba = os.path.join(postgresql_config_dir, "pg_hba.conf")
74@@ -1124,7 +1145,7 @@
75 update_service_port()
76 update_nrpe_checks()
77 write_metrics_cronjob('/usr/local/bin/postgres_to_statsd.py',
78- '/etc/cron.d/postgres_metrics')
79+ '/etc/cron.d/postgres_metrics')
80
81 # If an external mountpoint has caused an old, existing DB to be
82 # mounted, we need to ensure that all the users, databases, roles
83@@ -1578,6 +1599,7 @@
84
85 @hooks.hook('db-relation-joined', 'db-relation-changed')
86 def db_relation_joined_changed():
87+ reset_manual_replication_state()
88 if local_state['state'] == 'hot standby':
89 publish_hot_standby_credentials()
90 return
91@@ -1630,6 +1652,7 @@
92
93 @hooks.hook('db-admin-relation-joined', 'db-admin-relation-changed')
94 def db_admin_relation_joined_changed():
95+ reset_manual_replication_state()
96 if local_state['state'] == 'hot standby':
97 publish_hot_standby_credentials()
98 return
99@@ -2001,6 +2024,10 @@
100 authorized_units.add(unit)
101 local_state['authorized'] = authorized_units
102
103+ if hookenv.config('manual_replication'):
104+ log('manual_replication, nothing to do')
105+ return
106+
107 master = elected_master()
108
109 # Handle state changes:
110@@ -2150,18 +2177,21 @@
111
112 # Block until users and database has replicated, so we know the
113 # connection details we publish are actually valid. This will
114- # normally be pretty much instantaneous.
115- timeout = 60
116- start = time.time()
117- while time.time() < start + timeout:
118- cur = db_cursor(autocommit=True)
119- cur.execute('select datname from pg_database')
120- if cur.fetchone() is not None:
121- break
122- del cur
123- log('Waiting for database {} to be replicated'.format(
124- connection_settings['database']))
125- time.sleep(10)
126+ # normally be pretty much instantaneous. Do not block if we are
127+ # running in manual replication mode, as it is outside of juju's
128+ # control when replication is actually setup and running.
129+ if not hookenv.config('manual_replication'):
130+ timeout = 60
131+ start = time.time()
132+ while time.time() < start + timeout:
133+ cur = db_cursor(autocommit=True)
134+ cur.execute('select datname from pg_database')
135+ if cur.fetchone() is not None:
136+ break
137+ del cur
138+ log('Waiting for database {} to be replicated'.format(
139+ connection_settings['database']))
140+ time.sleep(10)
141
142 log("Relation {} connection settings {!r}".format(
143 client_relation, connection_settings), DEBUG)
144
145=== modified file 'test.py'
146--- test.py 2014-10-14 09:12:29 +0000
147+++ test.py 2014-12-04 17:50:16 +0000
148@@ -209,10 +209,11 @@
149 '{} not yet authorized by {} ({})'.format(
150 psql_unit, unit, allowed_units))
151
152+ # Alas, this is no longer true with manual replication.
153 # We must not have multiple masters in this relation.
154- if len(rel_pg_units) > 1 and num_masters != 1:
155- raise NotReady(
156- '{} masters'.format(num_masters))
157+ # if len(rel_pg_units) > 1 and num_masters != 1:
158+ # raise NotReady(
159+ # '{} masters'.format(num_masters))
160
161 if pg_units != all_rel_pg_units:
162 raise NotReady(
163@@ -271,8 +272,9 @@
164 elif peer_state != 'hot standby':
165 raise NotReady('Peer {} in state {}'.format(peer, peer_state))
166
167- if num_masters != 1:
168- raise NotReady('No masters seen from {}'.format(pg_unit))
169+ # No longer true with manual replication.
170+ # if num_masters < 1:
171+ # raise NotReady('No masters seen from {}'.format(pg_unit))
172
173 def sql(self, sql, postgres_unit=None, psql_unit=None, dbname=None):
174 '''Run some SQL on postgres_unit from psql_unit.
175@@ -896,6 +898,29 @@
176 standby_data = self.sql('SELECT * FROM Foo', 'hot standby')[0][0]
177 self.assertTrue(standby_data)
178
179+ def test_manual_replication(self):
180+ self.pg_config['manual_replication'] = True
181+ self.juju.deploy(
182+ TEST_CHARM, 'postgresql', num_units=2, config=self.pg_config)
183+ self.juju.deploy(PSQL_CHARM, 'psql')
184+ self.juju.do(['add-relation', 'postgresql:db', 'psql:db'])
185+ pg_units = ['postgresql/0', 'postgresql/1']
186+ self.wait_until_ready(pg_units)
187+
188+ # Because we have enabled manual_replication mode, and not
189+ # actually manually configured replication, we will have
190+ # two masters.
191+ self.assertTrue(self.is_master('postgresql/0'))
192+ self.assertTrue(self.is_master('postgresql/1'))
193+
194+ # Both units are advertised as master on the relation.
195+ rel_info = self.juju.relation_info('psql/0')
196+ for relname in rel_info:
197+ for relid in rel_info[relname]:
198+ for unit in pg_units:
199+ self.assertEqual(rel_info[relname][relid][unit]['state'],
200+ 'master')
201+
202
203 class PG91Tests(
204 PostgreSQLCharmBaseTestCase,

Subscribers

People subscribed via source and target branches