Merge lp:~stub/launchpad/db-deploy into lp:launchpad

Proposed by Stuart Bishop
Status: Merged
Approved by: Stuart Bishop
Approved revision: no longer in the source branch.
Merged at revision: 14433
Proposed branch: lp:~stub/launchpad/db-deploy
Merge into: lp:launchpad
Diff against target: 271 lines (+108/-51)
2 files modified
database/schema/security.cfg (+1/-1)
database/schema/upgrade.py (+107/-50)
To merge this branch: bzr merge lp:~stub/launchpad/db-deploy
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Review via email: mp+84231@code.launchpad.net

Commit message

[r=adeuring][no-qa] Record bzr branch details when applying database updates

Description of the change

Record bzr branch details when applying database updates

To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) wrote :

Looks good.

+ def add_sql(sql):
+ sql = sql.strip()
+ if sql != '':
+ assert sql.endswith(';'), "SQL fragment not terminated with ';'"

 I'm wondering it if makes sense to add the statement(s)triggering the assert to the error message.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2011-11-28 20:07:20 +0000
+++ database/schema/security.cfg 2011-12-03 04:54:33 +0000
@@ -48,6 +48,7 @@
48public.is_team(text) = EXECUTE48public.is_team(text) = EXECUTE
49public.latestdatabasediskutilization =49public.latestdatabasediskutilization =
50public.launchpaddatabaserevision = SELECT50public.launchpaddatabaserevision = SELECT
51public.launchpaddatabaseupdatelog = SELECT
51public.lp_account =52public.lp_account =
52public.lp_openididentifier =53public.lp_openididentifier =
53public.lp_person =54public.lp_person =
@@ -1142,7 +1143,6 @@
1142public.karma = SELECT, INSERT, UPDATE1143public.karma = SELECT, INSERT, UPDATE
1143public.karmaaction = SELECT, INSERT, UPDATE1144public.karmaaction = SELECT, INSERT, UPDATE
1144public.language = SELECT, INSERT, UPDATE1145public.language = SELECT, INSERT, UPDATE
1145public.launchpaddatabaserevision = SELECT, INSERT, UPDATE
1146public.libraryfilealias = SELECT, INSERT1146public.libraryfilealias = SELECT, INSERT
1147public.libraryfilecontent = SELECT, INSERT1147public.libraryfilecontent = SELECT, INSERT
1148public.logintoken = SELECT, INSERT, UPDATE1148public.logintoken = SELECT, INSERT, UPDATE
11491149
=== modified file 'database/schema/upgrade.py'
--- database/schema/upgrade.py 2011-10-10 14:58:41 +0000
+++ database/schema/upgrade.py 2011-12-03 04:54:33 +0000
@@ -19,9 +19,14 @@
19import re19import re
20from tempfile import NamedTemporaryFile20from tempfile import NamedTemporaryFile
21from textwrap import dedent21from textwrap import dedent
22import subprocess
2223
23from canonical.launchpad.scripts import db_options, logger_options, logger24from canonical.launchpad.scripts import db_options, logger_options, logger
24from canonical.database.sqlbase import connect, ISOLATION_LEVEL_AUTOCOMMIT25from canonical.database.sqlbase import (
26 connect,
27 ISOLATION_LEVEL_AUTOCOMMIT,
28 sqlvalues,
29 )
25from canonical.database.postgresql import fqn30from canonical.database.postgresql import fqn
26import replication.helpers31import replication.helpers
2732
@@ -90,12 +95,26 @@
90 AND LaunchpadDatabaseRevision.start_time <> prev_end_time;95 AND LaunchpadDatabaseRevision.start_time <> prev_end_time;
9196
92 UPDATE LaunchpadDatabaseRevision97 UPDATE LaunchpadDatabaseRevision
93 SET start_time=_start_time.start_time98 SET
99 start_time=_start_time.start_time,
100 branch_nick = %s,
101 revno = %s,
102 revid = %s
94 FROM _start_time103 FROM _start_time
95 WHERE104 WHERE
96 LaunchpadDatabaseRevision.start_time105 LaunchpadDatabaseRevision.start_time
97 = transaction_timestamp() AT TIME ZONE 'UTC';106 = transaction_timestamp() AT TIME ZONE 'UTC';
98 """)107 """)
108START_UPDATE_LOG_SQL = dedent("""\
109 INSERT INTO LaunchpadDatabaseUpdateLog (
110 start_time, end_time, branch_nick, revno, revid)
111 VALUES (transaction_timestamp() AT TIME ZONE 'UTC', NULL, %s, %s, %s);
112 """)
113FINISH_UPDATE_LOG_SQL = dedent("""\
114 UPDATE LaunchpadDatabaseUpdateLog
115 SET end_time = statement_timestamp() AT TIME ZONE 'UTC'
116 WHERE start_time = transaction_timestamp() AT TIME ZONE 'UTC';
117 """)
99118
100119
101def to_seconds(td):120def to_seconds(td):
@@ -148,12 +167,31 @@
148167
149def apply_patches_normal(con):168def apply_patches_normal(con):
150 """Update a non replicated database."""169 """Update a non replicated database."""
170 # On dev environments, until we create a fresh database baseline the
171 # LaunchpadDatabaseUpdateLog tables does not exist at this point (it
172 # will be created later via database patch). Don't try to update
173 # LaunchpadDatabaseUpdateLog if it does not exist.
174 cur = con.cursor()
175 cur.execute("""
176 SELECT EXISTS (
177 SELECT TRUE FROM information_schema.tables
178 WHERE
179 table_schema='public'
180 AND table_name='launchpaddatabaseupdatelog')
181 """)
182 updatelog_exists = cur.fetchone()[0]
183
184 # Add a record to LaunchpadDatabaseUpdateLog that we are starting
185 # an update.
186 if updatelog_exists:
187 cur.execute(START_UPDATE_LOG_SQL % sqlvalues(*get_bzr_details()))
188
151 # trusted.sql contains all our stored procedures, which may189 # trusted.sql contains all our stored procedures, which may
152 # be required for patches to apply correctly so must be run first.190 # be required for patches to apply correctly so must be run first.
153 apply_other(con, 'trusted.sql')191 apply_other(con, 'trusted.sql')
154192
155 # Prepare to repair patch timestamps if necessary.193 # Prepare to repair patch timestamps if necessary.
156 con.cursor().execute(FIX_PATCH_TIMES_PRE_SQL)194 cur.execute(FIX_PATCH_TIMES_PRE_SQL)
157195
158 # Apply the patches196 # Apply the patches
159 patches = get_patchlist(con)197 patches = get_patchlist(con)
@@ -161,11 +199,17 @@
161 apply_patch(con, major, minor, patch, patch_file)199 apply_patch(con, major, minor, patch, patch_file)
162200
163 # Repair patch timestamps if necessary.201 # Repair patch timestamps if necessary.
164 con.cursor().execute(FIX_PATCH_TIMES_POST_SQL)202 cur.execute(
203 FIX_PATCH_TIMES_POST_SQL % sqlvalues(*get_bzr_details()))
165204
166 # Update comments.205 # Update comments.
167 apply_comments(con)206 apply_comments(con)
168207
208 # Update the LaunchpadDatabaseUpdateLog record, stating the
209 # completion time.
210 if updatelog_exists:
211 cur.execute(FINISH_UPDATE_LOG_SQL)
212
169213
170def apply_patches_replicated():214def apply_patches_replicated():
171 """Update a Slony-I cluster."""215 """Update a Slony-I cluster."""
@@ -181,73 +225,54 @@
181 log.info("Waiting for cluster to sync, pre-update.")225 log.info("Waiting for cluster to sync, pre-update.")
182 replication.helpers.sync(timeout=600)226 replication.helpers.sync(timeout=600)
183227
228 # Slonik script we are generating.
184 outf = StringIO()229 outf = StringIO()
185230
186 # Start a transaction block.231 # Start a transaction block.
187 print >> outf, "try {"232 print >> outf, "try {"
188233
189 sql_to_run = []234 # All the SQL we need to run, combined into one file. This minimizes
190235 # Slony-I syncs and downtime.
191 def run_sql(script):236 combined_sql = NamedTemporaryFile(prefix='dbupdate', suffix='.sql')
192 if os.path.isabs(script):237
193 full_path = script238 def add_sql(sql):
194 else:239 sql = sql.strip()
195 full_path = os.path.abspath(os.path.join(SCHEMA_DIR, script))240 if sql != '':
196 assert os.path.exists(full_path), "%s doesn't exist." % full_path241 assert sql.endswith(';'), "SQL not terminated with ';': %s" % sql
197 sql_to_run.append(full_path)242 print >> combined_sql, sql
198243 # Flush or we might lose statements from buffering.
199 # We are going to generate some temporary files using244 combined_sql.flush()
200 # NamedTempoararyFile. Store them here so we can control when245
201 # they get closed and cleaned up.246 # Add a LaunchpadDatabaseUpdateLog record that we are starting patch
202 temporary_files = []247 # application.
248 add_sql(START_UPDATE_LOG_SQL % sqlvalues(*get_bzr_details()))
203249
204 # Apply trusted.sql250 # Apply trusted.sql
205 run_sql('trusted.sql')251 add_sql(open(os.path.join(SCHEMA_DIR, 'trusted.sql'), 'r').read())
206
207 # We are going to generate some temporary files using
208 # NamedTempoararyFile. Store them here so we can control when
209 # they get closed and cleaned up.
210 temporary_files = []
211
212 # Apply DB patches as one big hunk.
213 combined_script = NamedTemporaryFile(prefix='patch', suffix='.sql')
214 temporary_files.append(combined_script)
215252
216 # Prepare to repair the start timestamps in253 # Prepare to repair the start timestamps in
217 # LaunchpadDatabaseRevision.254 # LaunchpadDatabaseRevision.
218 print >> combined_script, FIX_PATCH_TIMES_PRE_SQL255 add_sql(FIX_PATCH_TIMES_PRE_SQL)
219256
220 patches = get_patchlist(con)257 patches = get_patchlist(con)
221 for (major, minor, patch), patch_file in patches:258 for (major, minor, patch), patch_file in patches:
222 print >> combined_script, open(patch_file, 'r').read()259 add_sql(open(patch_file, 'r').read())
223260
224 # Trigger a failure if the patch neglected to update261 # Trigger a failure if the patch neglected to update
225 # LaunchpadDatabaseRevision.262 # LaunchpadDatabaseRevision.
226 print >> combined_script, (263 add_sql(
227 "SELECT assert_patch_applied(%d, %d, %d);"264 "SELECT assert_patch_applied(%d, %d, %d);"
228 % (major, minor, patch))265 % (major, minor, patch))
229266
230 # Fix the start timestamps in LaunchpadDatabaseRevision.267 # Fix the start timestamps in LaunchpadDatabaseRevision.
231 print >> combined_script, FIX_PATCH_TIMES_POST_SQL268 add_sql(FIX_PATCH_TIMES_POST_SQL % sqlvalues(*get_bzr_details()))
232
233 combined_script.flush()
234 run_sql(combined_script.name)
235
236 # Now combine all the written SQL (probably trusted.sql and
237 # patch*.sql) into one big file, which we execute with a single
238 # slonik execute_script statement to avoid multiple syncs.
239 single = NamedTemporaryFile(prefix='single', suffix='.sql')
240 for path in sql_to_run:
241 print >> single, open(path, 'r').read()
242 print >> single, ""
243 single.flush()
244269
245 print >> outf, dedent("""\270 print >> outf, dedent("""\
246 execute script (271 execute script (
247 set id = @lpmain_set, event node = @master_node,272 set id = @lpmain_set, event node = @master_node,
248 filename='%s'273 filename='%s'
249 );274 );
250 """ % single.name)275 """ % combined_sql.name)
251276
252 # Close transaction block and abort on error.277 # Close transaction block and abort on error.
253 print >> outf, dedent("""\278 print >> outf, dedent("""\
@@ -265,11 +290,6 @@
265 raise SystemExit(4)290 raise SystemExit(4)
266 log.info("slonik(1) schema upgrade script completed.")291 log.info("slonik(1) schema upgrade script completed.")
267292
268 # Cleanup our temporary files - they applied successfully.
269 for temporary_file in temporary_files:
270 temporary_file.close()
271 del temporary_files
272
273 # Wait for replication to sync.293 # Wait for replication to sync.
274 log.info("Waiting for patches to apply to slaves and cluster to sync.")294 log.info("Waiting for patches to apply to slaves and cluster to sync.")
275 replication.helpers.sync(timeout=0)295 replication.helpers.sync(timeout=0)
@@ -598,6 +618,43 @@
598 apply_other(con, 'comments.sql')618 apply_other(con, 'comments.sql')
599619
600620
621_bzr_details_cache = None
622
623
624def get_bzr_details():
625 """Return (branch_nick, revno, revision_id) of this Bazaar branch.
626
627 Returns (None, None, None) if the tree this code is running from
628 is not a Bazaar branch.
629 """
630 global _bzr_details_cache
631 if _bzr_details_cache is None:
632 cmd = [
633 'bzr',
634 'version-info',
635 '--custom',
636 '--template={branch_nick} {revno} {revision_id}',
637 SCHEMA_DIR,
638 ]
639 p = subprocess.Popen(
640 cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
641 stderr=subprocess.PIPE)
642 out, err = p.communicate()
643
644 if p.returncode == 0:
645 branch_nick, revno, revision_id = out.split(' ', 3)
646 log.debug("branch-nick: %s", branch_nick)
647 log.debug("revno: %s", revno)
648 log.debug("revision-id: %s", revision_id)
649 else:
650 log.error("Failed to retrieve Bazaar branch details")
651 revision_id, revno, branch_nick = None, None, None
652
653 _bzr_details_cache = (branch_nick, revno, revision_id)
654
655 return _bzr_details_cache
656
657
601if __name__ == '__main__':658if __name__ == '__main__':
602 parser = OptionParser()659 parser = OptionParser()
603 db_options(parser)660 db_options(parser)