Merge lp:~wgrant/launchpad/noro into lp:launchpad

Proposed by William Grant on 2012-08-09
Status: Merged
Approved by: William Grant on 2012-08-09
Approved revision: no longer in the source branch.
Merged at revision: 15779
Proposed branch: lp:~wgrant/launchpad/noro
Merge into: lp:launchpad
Diff against target: 1369 lines (+19/-889)
22 files modified
lib/lp/app/browser/configure.zcml (+1/-9)
lib/lp/app/stories/basics/xx-read-only-mode.txt (+0/-150)
lib/lp/services/config/__init__.py (+2/-14)
lib/lp/services/config/tests/test_database_config.py (+0/-25)
lib/lp/services/database/doc/db-policy.txt (+0/-27)
lib/lp/services/database/readonly.py (+0/-83)
lib/lp/services/database/tests/readonly.py (+0/-67)
lib/lp/services/database/tests/test_readonly.py (+0/-88)
lib/lp/services/feeds/configure.zcml (+1/-0)
lib/lp/services/webapp/adapter.py (+0/-47)
lib/lp/services/webapp/authorization.py (+0/-10)
lib/lp/services/webapp/dbpolicy.py (+9/-40)
lib/lp/services/webapp/errorlog.py (+1/-3)
lib/lp/services/webapp/interfaces.py (+0/-15)
lib/lp/services/webapp/login.py (+3/-29)
lib/lp/services/webapp/publication.py (+1/-42)
lib/lp/services/webapp/tests/test_dbpolicy.py (+0/-60)
lib/lp/services/webapp/tests/test_publication.py (+1/-147)
lib/lp/translations/browser/serieslanguage.py (+0/-7)
lib/lp/translations/browser/tests/test_distroserieslanguage_views.py (+0/-13)
lib/lp/translations/browser/translationmessage.py (+0/-6)
lib/lp/translations/model/pofile.py (+0/-7)
To merge this branch: bzr merge lp:~wgrant/launchpad/noro
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code 2012-08-09 Approve on 2012-08-09
Review via email: mp+118867@code.launchpad.net

Commit Message

Rip out read-only mode. Long live fastdowntime.

Description of the Change

This branch rips out all the non-config-related read-only mode code, since fastdowntime has rendered read-only mode obsolete. Pretty simple.

To post a comment you must log in.
Steve Kowalik (stevenk) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/browser/configure.zcml'
2--- lib/lp/app/browser/configure.zcml 2012-05-16 21:40:42 +0000
3+++ lib/lp/app/browser/configure.zcml 2012-08-09 04:24:19 +0000
4@@ -301,6 +301,7 @@
5 name="index.html"
6 permission="zope.Public"
7 class="lp.services.webapp.login.UnauthorizedView"
8+ template="../templates/launchpad-forbidden.pt"
9 attribute="__call__"
10 />
11
12@@ -421,15 +422,6 @@
13 class="lp.services.webapp.error.TranslationUnavailableView"
14 />
15
16- <!-- ReadOnlyModeViolation -->
17- <browser:page
18- for="lp.services.webapp.interfaces.ReadOnlyModeViolation"
19- name="index.html"
20- permission="zope.Public"
21- template="../templates/launchpad-readonlyfailure.pt"
22- class="lp.services.webapp.error.ReadOnlyErrorView"
23- />
24-
25 <!-- Vocabularies -->
26 <browser:page
27 for="*"
28
29=== removed file 'lib/lp/app/stories/basics/xx-read-only-mode.txt'
30--- lib/lp/app/stories/basics/xx-read-only-mode.txt 2011-12-24 17:49:30 +0000
31+++ lib/lp/app/stories/basics/xx-read-only-mode.txt 1970-01-01 00:00:00 +0000
32@@ -1,150 +0,0 @@
33-= Read-Only Mode =
34-
35-During upgrades, Launchpad can be put into read-only mode using a config
36-file switch. When in read-only mode, queries can be made to the slave
37-database but attempts to access the master database or make database
38-changes fail, returning an error page to the user.
39-
40- >>> from lp.services.database.tests.readonly import (
41- ... touch_read_only_file, remove_read_only_file)
42- >>> touch_read_only_file()
43-
44-
45-== Notification of read-only mode. ==
46-
47-Users are warned when Launchpad is running in read-only mode.
48-
49- >>> user_browser.open('http://launchpad.dev')
50- >>> print extract_text(first_tag_by_class(
51- ... user_browser.contents, 'warning message'))
52- Launchpad is undergoing maintenance
53- ...
54-
55-Anonymous users are also warned, or they might try to signup.
56-
57- >>> anon_browser.open("http://launchpad.dev/~name16")
58- >>> print extract_text(first_tag_by_class(
59- ... anon_browser.contents, 'warning message'))
60- Launchpad is undergoing maintenance
61- ...
62-
63-There is no warning when Launchpad is running normally.
64-
65- >>> remove_read_only_file()
66- >>> user_browser.open('http://launchpad.dev')
67- >>> print first_tag_by_class(
68- ... user_browser.contents, 'warning message')
69- None
70-
71-== Operations requiring write permissions fail ==
72-
73-In read-only mode, all requests for non-read permissions are denied.
74-This causes edit buttons and similar to not be displayed.
75-
76- >>> touch_read_only_file()
77- >>> user_browser.open('http://launchpad.dev/people/+me')
78- >>> user_browser.getLink('Change details')
79- Traceback (most recent call last):
80- ...
81- LinkNotFoundError
82-
83-Even if the user manages to follow a link to a form, such as clicking
84-on a link rendered before read-only mode was switched on or someone
85-forgetting to properly protect the edit buttons, the form is replaced
86-with a nice 503 error page informing the user what is going on.
87-
88- >>> remove_read_only_file()
89- >>> user_browser.open('http://launchpad.dev/people/+me')
90- >>> edit_link = user_browser.getLink('Change details')
91- >>> edit_link is None
92- False
93- >>> # XXX StuartBishop 20090423 bug=365378: raiseHttpErrors is broken,
94- >>> # requiring the try/except dance.
95- >>> user_browser.handleErrors = True
96- >>> user_browser.raiseHttpErrors = False
97- >>> touch_read_only_file()
98- >>> try:
99- ... edit_link.click()
100- ... except:
101- ... pass
102- >>> print user_browser.headers.get('Status')
103- 503 Service Unavailable
104- >>> print extract_text(first_tag_by_class(
105- ... user_browser.contents, 'exception'))
106- Sorry, you can't do this right now
107-
108-And even if a user manages to get a form and submit it, they get the
109-same 503 error page.
110-
111- >>> remove_read_only_file()
112- >>> user_browser.handleErrors = True
113- >>> user_browser.open('http://launchpad.dev/people/+me')
114- >>> user_browser.getLink('Change details').click()
115- >>> user_browser.getControl(name='field.displayname').value = 'Different'
116- >>> user_browser.raiseHttpErrors = False
117- >>> # XXX StuartBishop 20090423 bug=365378: raiseHttpErrors is broken,
118- >>> # requiring the try/except dance.
119- >>> touch_read_only_file()
120- >>> try:
121- ... user_browser.getControl('Save Changes').click()
122- ... except:
123- ... pass
124- >>> print user_browser.headers.get('Status')
125- 503 Service Unavailable
126- >>> print extract_text(first_tag_by_class(
127- ... user_browser.contents, 'exception'))
128- Sorry, you can't do this right now
129-
130-There are actually two exceptions that might trigger this error page.
131-
132- * Legacy code may trigger the ReadOnlyModeViolation exception by
133- attempting to write to an object retrieved from the default Store.
134-
135- * Code may trigger the ReadOnlyModeDisallowedStore exception by
136- requesting a master Store.
137-
138-Unfortunately it is difficult to ensure the same exception will
139-continue to be raised by the above test. Instead, we confirm that both
140-exceptions are rendered using the same view ensuring that the observed
141-behavior is the same.
142-
143- >>> from zope.app import zapi
144- >>> from lp.services.webapp.interfaces import (
145- ... ReadOnlyModeDisallowedStore, ReadOnlyModeViolation)
146- >>> from lp.services.webapp.servers import LaunchpadTestRequest
147- >>> request = LaunchpadTestRequest()
148- >>> view_name = zapi.queryDefaultViewName(
149- ... ReadOnlyModeDisallowedStore, request)
150- >>> view_name is not None
151- True
152- >>> disallowed_view = zapi.queryMultiAdapter(
153- ... (ReadOnlyModeDisallowedStore, request), name=view_name)
154-
155- >>> view_name = zapi.queryDefaultViewName(
156- ... ReadOnlyModeViolation, request)
157- >>> view_name is not None
158- True
159- >>> violation_view = zapi.queryMultiAdapter(
160- ... (ReadOnlyModeViolation, request), name=view_name)
161-
162- >>> violation_view == disallowed_view
163- True
164-
165-
166-== Read-only pages ==
167-
168-Most Launchpad pages (the ones that don't handle edit forms) can be
169-accessed in read-only mode. Here are some examples.
170-
171-=== Bug page ===
172-
173- >>> user_browser.open('http://launchpad.dev/bugs/5')
174- >>> print user_browser.title
175- Bug #5...
176- >>> print user_browser.headers['status']
177- 200 Ok
178-
179-
180-== Cleanup ==
181-
182- >>> remove_read_only_file()
183
184=== modified file 'lib/lp/services/config/__init__.py'
185--- lib/lp/services/config/__init__.py 2012-06-29 08:40:05 +0000
186+++ lib/lp/services/config/__init__.py 2012-08-09 04:24:19 +0000
187@@ -433,23 +433,11 @@
188
189 @property
190 def main_master(self):
191- # Its a bit silly having ro_main_master and rw_main_master.
192- # rw_main_master will never be used, as read-only-mode will
193- # fail attempts to access the master database with a
194- # ReadOnlyModeDisallowedStore exception.
195- from lp.services.database.readonly import is_read_only
196- if is_read_only():
197- return self.ro_main_master
198- else:
199- return self.rw_main_master
200+ return self.rw_main_master
201
202 @property
203 def main_slave(self):
204- from lp.services.database.readonly import is_read_only
205- if is_read_only():
206- return self.ro_main_slave
207- else:
208- return self.rw_main_slave
209+ return self.rw_main_slave
210
211 def override(self, **kwargs):
212 """Override one or more config attributes.
213
214=== modified file 'lib/lp/services/config/tests/test_database_config.py'
215--- lib/lp/services/config/tests/test_database_config.py 2012-04-06 17:28:25 +0000
216+++ lib/lp/services/config/tests/test_database_config.py 2012-08-09 04:24:19 +0000
217@@ -4,11 +4,6 @@
218 __metaclass__ = type
219
220 from lp.services.config import DatabaseConfig
221-from lp.services.database.readonly import read_only_file_exists
222-from lp.services.database.tests.readonly import (
223- remove_read_only_file,
224- touch_read_only_file,
225- )
226 from lp.testing import TestCase
227 from lp.testing.layers import DatabaseLayer
228
229@@ -46,23 +41,3 @@
230 self.assertEqual('not_launchpad', dbc.dbuser)
231 dbc.reset()
232 self.assertEqual('launchpad_main', dbc.dbuser)
233-
234- def test_main_master_and_main_slave(self):
235- # DatabaseConfig provides two extra properties: main_master and
236- # main_slave, which return the value of either
237- # rw_main_master/rw_main_slave or ro_main_master/ro_main_slave,
238- # depending on whether or not we're in read-only mode.
239- dbc = DatabaseConfig()
240- self.assertFalse(read_only_file_exists())
241- self.assertEquals(dbc.rw_main_master, dbc.main_master)
242- self.assertEquals(dbc.rw_main_slave, dbc.main_slave)
243-
244- touch_read_only_file()
245- try:
246- self.assertTrue(read_only_file_exists())
247- self.assertEquals(
248- dbc.ro_main_master, dbc.main_master)
249- self.assertEquals(
250- dbc.ro_main_slave, dbc.main_slave)
251- finally:
252- remove_read_only_file()
253
254=== modified file 'lib/lp/services/database/doc/db-policy.txt'
255--- lib/lp/services/database/doc/db-policy.txt 2012-02-02 10:26:54 +0000
256+++ lib/lp/services/database/doc/db-policy.txt 2012-08-09 04:24:19 +0000
257@@ -123,30 +123,3 @@
258 >>> from lp.services.database.lpstorm import IMasterObject
259 >>> IMasterObject(ro_janitor) is writable_janitor
260 True
261-
262-Read-Only Mode
263---------------
264-
265-During database outages, we run in read-only mode. In this mode, no
266-matter what database policy is currently installed, explicit requests
267-for a master store fail and the default store is always the slave.
268-
269- >>> from lp.services.database.tests.readonly import read_only_mode
270- >>> from lp.services.webapp.dbpolicy import MasterDatabasePolicy
271- >>> from contextlib import nested
272-
273- >>> with nested(read_only_mode(), MasterDatabasePolicy()):
274- ... default_store = IStore(Person)
275- ... IMasterStore.providedBy(default_store)
276- False
277-
278- >>> with nested(read_only_mode(), MasterDatabasePolicy()):
279- ... slave_store = ISlaveStore(Person)
280- ... IMasterStore.providedBy(slave_store)
281- False
282-
283- >>> with nested(read_only_mode(), MasterDatabasePolicy()):
284- ... master_store = IMasterStore(Person)
285- Traceback (most recent call last):
286- ...
287- ReadOnlyModeDisallowedStore: ('main', 'master')
288
289=== removed file 'lib/lp/services/database/readonly.py'
290--- lib/lp/services/database/readonly.py 2012-01-01 02:58:52 +0000
291+++ lib/lp/services/database/readonly.py 1970-01-01 00:00:00 +0000
292@@ -1,83 +0,0 @@
293-# Copyright 2010 Canonical Ltd. This software is licensed under the
294-# GNU Affero General Public License version 3 (see the file LICENSE).
295-
296-"""Helpers for running Launchpad in read-only mode.
297-
298-To switch an app server to read-only mode, all you need to do is create a file
299-named read-only.txt in the root of the Launchpad tree.
300-"""
301-
302-__metaclass__ = type
303-__all__ = [
304- 'is_read_only',
305- 'read_only_file_exists',
306- 'read_only_file_path',
307- ]
308-
309-import logging
310-import os
311-import threading
312-
313-from lazr.restful.utils import get_current_browser_request
314-from zope.security.management import queryInteraction
315-
316-from lp.services.config import config
317-
318-
319-read_only_file_path = os.path.join(config.root, 'read-only.txt')
320-READ_ONLY_MODE_ANNOTATIONS_KEY = 'launchpad.read_only_mode'
321-
322-
323-def read_only_file_exists():
324- """Does a read-only.txt file exists in the root of our tree?"""
325- return os.path.isfile(read_only_file_path)
326-
327-
328-_lock = threading.Lock()
329-_currently_in_read_only = False
330-
331-
332-def is_read_only():
333- """Are we in read-only mode?
334-
335- If called as part of the processing of a request, we'll look in the
336- request's annotations for a read-only key
337- (READ_ONLY_MODE_ANNOTATIONS_KEY), and if it exists we'll just return its
338- value.
339-
340- If there's no request or the key doesn't exist, we check for the presence
341- of a read-only.txt file in the root of our tree, set the read-only key in
342- the request's annotations (when there is a request), update
343- _currently_in_read_only (in case it changed, also logging the change)
344- and return it.
345- """
346- # pylint: disable-msg=W0603
347- global _currently_in_read_only
348- request = None
349- # XXX: salgado, 2010-01-14, bug=507447: Only call
350- # get_current_browser_request() when we have an interaction, or else
351- # it will raise an AttributeError.
352- if queryInteraction() is not None:
353- request = get_current_browser_request()
354- if request is not None:
355- if READ_ONLY_MODE_ANNOTATIONS_KEY in request.annotations:
356- return request.annotations[READ_ONLY_MODE_ANNOTATIONS_KEY]
357-
358- read_only = read_only_file_exists()
359- if request is not None:
360- request.annotations[READ_ONLY_MODE_ANNOTATIONS_KEY] = read_only
361-
362- log_change = False
363- with _lock:
364- if _currently_in_read_only != read_only:
365- _currently_in_read_only = read_only
366- log_change = True
367-
368- if log_change:
369- logging.warning(
370- 'Read-only mode change detected; now read-only is %s' % read_only)
371-
372- return read_only
373-
374-
375-_currently_in_read_only = is_read_only()
376
377=== removed file 'lib/lp/services/database/tests/readonly.py'
378--- lib/lp/services/database/tests/readonly.py 2011-12-24 15:54:55 +0000
379+++ lib/lp/services/database/tests/readonly.py 1970-01-01 00:00:00 +0000
380@@ -1,67 +0,0 @@
381-# Copyright 2010 Canonical Ltd. This software is licensed under the
382-# GNU Affero General Public License version 3 (see the file LICENSE).
383-
384-"""Helpers for creating and removing a read-only.txt in the root of our tree.
385-"""
386-
387-__metaclass__ = type
388-__all__ = [
389- 'touch_read_only_file',
390- 'read_only_mode',
391- 'remove_read_only_file',
392- ]
393-
394-from contextlib import contextmanager
395-import os
396-
397-from lazr.restful.utils import get_current_browser_request
398-
399-from lp.services.database.readonly import (
400- is_read_only,
401- read_only_file_exists,
402- read_only_file_path,
403- READ_ONLY_MODE_ANNOTATIONS_KEY,
404- )
405-
406-
407-def touch_read_only_file():
408- """Create an empty file named read-only.txt under the root of the tree.
409-
410- This function must not be called if a file with that name already exists.
411- """
412- assert not read_only_file_exists(), (
413- "This function must not be called when a read-only.txt file "
414- "already exists.")
415- f = open(read_only_file_path, 'w')
416- f.close()
417- # Assert that the switch succeeded and make sure the mode change is
418- # logged.
419- assert is_read_only(), "Switching to read-only failed."
420-
421-
422-def remove_read_only_file(assert_mode_switch=True):
423- """Remove the file named read-only.txt from the root of the tree.
424-
425- May also assert that the mode switch actually happened (i.e. not
426- is_read_only()). This assertion has to be conditional because some tests
427- will use this during the processing of a request, when a mode change can't
428- happen (i.e. is_read_only() will still return True during that request's
429- processing, even though the read-only.txt file has been removed).
430- """
431- os.remove(read_only_file_path)
432- if assert_mode_switch:
433- # Assert that the switch succeeded and make sure the mode change is
434- # logged.
435- assert not is_read_only(), "Switching to read-write failed."
436-
437-
438-@contextmanager
439-def read_only_mode(flag=True):
440- request = get_current_browser_request()
441- current = request.annotations[READ_ONLY_MODE_ANNOTATIONS_KEY]
442- request.annotations[READ_ONLY_MODE_ANNOTATIONS_KEY] = flag
443- try:
444- assert is_read_only() == flag, 'Failed to set read-only mode'
445- yield
446- finally:
447- request.annotations[READ_ONLY_MODE_ANNOTATIONS_KEY] = current
448
449=== removed file 'lib/lp/services/database/tests/test_readonly.py'
450--- lib/lp/services/database/tests/test_readonly.py 2012-03-14 18:38:28 +0000
451+++ lib/lp/services/database/tests/test_readonly.py 1970-01-01 00:00:00 +0000
452@@ -1,88 +0,0 @@
453-# Copyright 2010 Canonical Ltd. This software is licensed under the
454-# GNU Affero General Public License version 3 (see the file LICENSE).
455-
456-__metaclass__ = type
457-
458-from lazr.restful.utils import get_current_browser_request
459-from zope.security.management import endInteraction
460-
461-from lp.services.database.readonly import (
462- is_read_only,
463- read_only_file_exists,
464- READ_ONLY_MODE_ANNOTATIONS_KEY,
465- )
466-from lp.services.database.tests.readonly import (
467- remove_read_only_file,
468- touch_read_only_file,
469- )
470-from lp.testing import (
471- ANONYMOUS,
472- login,
473- logout,
474- TestCase,
475- )
476-from lp.testing.layers import FunctionalLayer
477-
478-
479-class TestReadOnlyModeDetection(TestCase):
480-
481- def test_read_only_file_exists(self):
482- # By default we run in read-write mode.
483- self.assertFalse(read_only_file_exists())
484-
485- # When a file named 'read-only.txt' exists under the root of the tree,
486- # we run in read-only mode.
487- touch_read_only_file()
488- try:
489- self.assertTrue(read_only_file_exists())
490- finally:
491- remove_read_only_file()
492-
493- # Once the file is removed, we're back into read-write mode.
494- self.assertFalse(read_only_file_exists())
495-
496-
497-class Test_is_read_only(TestCase):
498- layer = FunctionalLayer
499-
500- def tearDown(self):
501- # Safety net just in case a test leaves the read-only.txt file behind.
502- if read_only_file_exists():
503- remove_read_only_file()
504- endInteraction()
505- super(Test_is_read_only, self).tearDown()
506-
507- def test_is_read_only(self):
508- # By default we run in read-write mode.
509- logout()
510- self.assertFalse(is_read_only())
511-
512- # When a file named 'read-only.txt' exists under the root of the tree,
513- # we run in read-only mode.
514- touch_read_only_file()
515- try:
516- self.assertTrue(is_read_only())
517- finally:
518- remove_read_only_file()
519-
520- def test_caching_in_request(self):
521- # When called as part of a request processing, is_read_only() will
522- # stash the read-only flag in the request's annotations.
523- login(ANONYMOUS)
524- request = get_current_browser_request()
525- self.assertIs(
526- None,
527- request.annotations.get(READ_ONLY_MODE_ANNOTATIONS_KEY))
528- self.assertFalse(is_read_only())
529- self.assertFalse(
530- request.annotations.get(READ_ONLY_MODE_ANNOTATIONS_KEY))
531-
532- def test_cached_value_takes_precedence(self):
533- # Once the request has the read-only flag, we don't check for the
534- # presence of the read-only.txt file anymore, so it could be removed
535- # and the request would still be in read-only mode.
536- login(ANONYMOUS)
537- request = get_current_browser_request()
538- request.annotations[READ_ONLY_MODE_ANNOTATIONS_KEY] = True
539- self.assertTrue(is_read_only())
540- self.assertFalse(read_only_file_exists())
541
542=== modified file 'lib/lp/services/feeds/configure.zcml'
543--- lib/lp/services/feeds/configure.zcml 2011-12-30 07:32:58 +0000
544+++ lib/lp/services/feeds/configure.zcml 2012-08-09 04:24:19 +0000
545@@ -46,6 +46,7 @@
546 name="index.html"
547 permission="zope.Public"
548 class="lp.services.webapp.login.FeedsUnauthorizedView"
549+ template="../../app/templates/launchpad-forbidden.pt"
550 attribute="__call__"
551 layer="lp.layers.FeedsLayer"
552 />
553
554=== modified file 'lib/lp/services/webapp/adapter.py'
555--- lib/lp/services/webapp/adapter.py 2012-06-29 08:40:05 +0000
556+++ lib/lp/services/webapp/adapter.py 2012-08-09 04:24:19 +0000
557@@ -21,7 +21,6 @@
558 get_current_browser_request,
559 safe_hasattr,
560 )
561-import psycopg2
562 from psycopg2.extensions import (
563 ISOLATION_LEVEL_AUTOCOMMIT,
564 ISOLATION_LEVEL_READ_COMMITTED,
565@@ -36,7 +35,6 @@
566 )
567 from storm.databases.postgres import (
568 Postgres,
569- PostgresConnection,
570 PostgresTimeoutTracer,
571 )
572 from storm.exceptions import TimeoutError
573@@ -65,7 +63,6 @@
574 IMasterStore,
575 )
576 from lp.services.database.postgresql import ConnectionString
577-from lp.services.database.readonly import is_read_only
578 from lp.services.log.loglevels import DEBUG2
579 from lp.services.stacktrace import (
580 extract_stack,
581@@ -84,8 +81,6 @@
582 IStoreSelector,
583 MAIN_STORE,
584 MASTER_FLAVOR,
585- ReadOnlyModeDisallowedStore,
586- ReadOnlyModeViolation,
587 SLAVE_FLAVOR,
588 )
589 from lp.services.webapp.opstats import OpStats
590@@ -473,23 +468,6 @@
591 }
592
593
594-class ReadOnlyModeConnection(PostgresConnection):
595- """storm.database.Connection for read-only mode Launchpad."""
596-
597- def execute(self, statement, params=None, noresult=False):
598- """See storm.database.Connection."""
599- try:
600- return super(ReadOnlyModeConnection, self).execute(
601- statement, params, noresult)
602- except psycopg2.InternalError as exception:
603- # Error 25006 is 'ERROR: transaction is read-only'. This
604- # is raised when an attempt is made to make changes when
605- # the connection has been put in read-only mode.
606- if exception.pgcode == '25006':
607- raise ReadOnlyModeViolation(None, sys.exc_info()[2])
608- raise
609-
610-
611 class LaunchpadDatabase(Postgres):
612
613 _dsn_user_re = re.compile('user=[^ ]*')
614@@ -572,17 +550,6 @@
615 flavor, raw_connection.get_backend_pid(), dbuser, self._isolation)
616 return raw_connection
617
618- @property
619- def connection_factory(self):
620- """Return the correct connection factory for the current mode.
621-
622- If we are running in read-only mode, returns a
623- ReadOnlyModeConnection. Otherwise it returns the Storm default.
624- """
625- if is_read_only():
626- return ReadOnlyModeConnection
627- return super(LaunchpadDatabase, self).connection_factory
628-
629
630 class LaunchpadSessionDatabase(Postgres):
631
632@@ -828,20 +795,6 @@
633 @staticmethod
634 def get(name, flavor):
635 """See `IStoreSelector`."""
636- if is_read_only():
637- # If we are in read-only mode, override the default to the
638- # slave no matter what the existing policy says (it might
639- # work), and raise an exception if the master was explicitly
640- # requested. Most of the time, this doesn't matter as when
641- # we are in read-only mode we have a suitable database
642- # policy installed. However, code can override the policy so
643- # we still need to catch disallowed requests here.
644- if flavor == DEFAULT_FLAVOR:
645- flavor = SLAVE_FLAVOR
646- elif flavor == MASTER_FLAVOR:
647- raise ReadOnlyModeDisallowedStore(name, flavor)
648- else:
649- pass
650 db_policy = StoreSelector.get_current()
651 if db_policy is None:
652 db_policy = MasterDatabasePolicy(None)
653
654=== modified file 'lib/lp/services/webapp/authorization.py'
655--- lib/lp/services/webapp/authorization.py 2012-03-31 11:32:15 +0000
656+++ lib/lp/services/webapp/authorization.py 2012-08-09 04:24:19 +0000
657@@ -40,7 +40,6 @@
658
659 from lp.app.interfaces.security import IAuthorization
660 from lp.registry.interfaces.role import IPersonRoles
661-from lp.services.database.readonly import is_read_only
662 from lp.services.database.sqlbase import block_implicit_flushes
663 from lp.services.privacy.interfaces import IObjectPrivacy
664 from lp.services.webapp.canonicalurl import nearest_adapter
665@@ -144,15 +143,6 @@
666 after the permission, use that to check the permission.
667 - Otherwise, deny.
668 """
669- # Shortcut in read-only mode. We have to do this now to avoid
670- # accidentally using cached results. This will be important when
671- # Launchpad automatically fails over to read-only mode when the
672- # master database is unavailable.
673- if is_read_only():
674- lp_permission = getUtility(ILaunchpadPermission, permission)
675- if lp_permission.access_level != "read":
676- return False
677-
678 # If we have a view, get its context and use that to get an
679 # authorization adapter.
680 if IView.providedBy(object):
681
682=== modified file 'lib/lp/services/webapp/dbpolicy.py'
683--- lib/lp/services/webapp/dbpolicy.py 2012-06-20 18:48:02 +0000
684+++ lib/lp/services/webapp/dbpolicy.py 2012-08-09 04:24:19 +0000
685@@ -9,7 +9,6 @@
686 'DatabaseBlockedPolicy',
687 'LaunchpadDatabasePolicy',
688 'MasterDatabasePolicy',
689- 'ReadOnlyLaunchpadDatabasePolicy',
690 'SlaveDatabasePolicy',
691 'SlaveOnlyDatabasePolicy',
692 ]
693@@ -45,7 +44,6 @@
694 IMasterStore,
695 ISlaveStore,
696 )
697-from lp.services.database.readonly import is_read_only
698 from lp.services.database.sqlbase import StupidCache
699 from lp.services.webapp import LaunchpadView
700 from lp.services.webapp.interfaces import (
701@@ -55,7 +53,6 @@
702 IStoreSelector,
703 MAIN_STORE,
704 MASTER_FLAVOR,
705- ReadOnlyModeDisallowedStore,
706 SLAVE_FLAVOR,
707 )
708
709@@ -195,8 +192,6 @@
710 # of test requests in our automated tests.
711 if request.get('PATH_INFO') in [u'/+opstats', u'/+haproxy']:
712 return DatabaseBlockedPolicy(request)
713- elif is_read_only():
714- return ReadOnlyLaunchpadDatabasePolicy(request)
715 else:
716 return LaunchpadDatabasePolicy(request)
717
718@@ -357,41 +352,15 @@
719 def WebServiceDatabasePolicyFactory(request):
720 """Return the Launchpad IDatabasePolicy for the current appserver state.
721 """
722- if is_read_only():
723- return ReadOnlyLaunchpadDatabasePolicy(request)
724- else:
725- # If a session cookie was sent with the request, use the
726- # standard Launchpad database policy for load balancing to
727- # the slave databases. The javascript web service libraries
728- # send the session cookie for authenticated users.
729- cookie_name = getUtility(IClientIdManager).namespace
730- if cookie_name in request.cookies:
731- return LaunchpadDatabasePolicy(request)
732- # Otherwise, use the master only web service database policy.
733- return MasterDatabasePolicy(request)
734-
735-
736-class ReadOnlyLaunchpadDatabasePolicy(BaseDatabasePolicy):
737- """Policy for Launchpad web requests when running in read-only mode.
738-
739- Access to all master Stores is blocked.
740- """
741-
742- def getStore(self, name, flavor):
743- """See `IDatabasePolicy`.
744-
745- Access to all master Stores is blocked. The default Store is
746- the slave.
747-
748- Note that we even have to block access to the authdb master
749- Store, as it allows access to tables replicated from the
750- lpmain replication set. These tables will be locked during
751- a lpmain replication set database upgrade.
752- """
753- if flavor == MASTER_FLAVOR:
754- raise ReadOnlyModeDisallowedStore(name, flavor)
755- return super(ReadOnlyLaunchpadDatabasePolicy, self).getStore(
756- name, SLAVE_FLAVOR)
757+ # If a session cookie was sent with the request, use the
758+ # standard Launchpad database policy for load balancing to
759+ # the slave databases. The javascript web service libraries
760+ # send the session cookie for authenticated users.
761+ cookie_name = getUtility(IClientIdManager).namespace
762+ if cookie_name in request.cookies:
763+ return LaunchpadDatabasePolicy(request)
764+ # Otherwise, use the master only web service database policy.
765+ return MasterDatabasePolicy(request)
766
767
768 class WhichDbView(LaunchpadView):
769
770=== modified file 'lib/lp/services/webapp/errorlog.py'
771--- lib/lp/services/webapp/errorlog.py 2012-05-15 07:15:17 +0000
772+++ lib/lp/services/webapp/errorlog.py 2012-08-09 04:24:19 +0000
773@@ -294,9 +294,7 @@
774 class ErrorReportingUtility:
775 implements(IErrorReportingUtility)
776
777- _ignored_exceptions = set([
778- 'ReadOnlyModeDisallowedStore', 'ReadOnlyModeViolation',
779- 'TranslationUnavailable', 'NoReferrerError'])
780+ _ignored_exceptions = set(['TranslationUnavailable', 'NoReferrerError'])
781 _ignored_exceptions_for_offsite_referer = set([
782 'GoneError', 'InvalidBatchSizeError', 'NotFound'])
783 _default_config_section = 'error_reports'
784
785=== modified file 'lib/lp/services/webapp/interfaces.py'
786--- lib/lp/services/webapp/interfaces.py 2012-02-22 18:51:23 +0000
787+++ lib/lp/services/webapp/interfaces.py 2012-08-09 04:24:19 +0000
788@@ -805,21 +805,6 @@
789 """
790
791
792-class ReadOnlyModeViolation(Exception):
793- """An attempt was made to write to a slave Store in read-only mode.
794-
795- This can happen in legacy code where writes are being made to an
796- object retrieved from the default Store rather than casting the
797- object to a writable version using IMasterObject(obj).
798- """
799-
800-
801-class ReadOnlyModeDisallowedStore(DisallowedStore, ReadOnlyModeViolation):
802- """A request was made to access a Store that cannot be granted
803- because we are running in read-only mode.
804- """
805-
806-
807 class IStoreSelector(Interface):
808 """Get a Storm store with a desired flavor.
809
810
811=== modified file 'lib/lp/services/webapp/login.py'
812--- lib/lp/services/webapp/login.py 2012-08-08 14:27:28 +0000
813+++ lib/lp/services/webapp/login.py 2012-08-09 04:24:19 +0000
814@@ -45,7 +45,6 @@
815 TeamEmailAddressError,
816 )
817 from lp.services.config import config
818-from lp.services.database.readonly import is_read_only
819 from lp.services.identity.interfaces.account import AccountSuspendedError
820 from lp.services.openid.interfaces.openidconsumer import IOpenIDConsumerStore
821 from lp.services.propertycache import cachedproperty
822@@ -59,7 +58,6 @@
823 IPlacelessLoginSource,
824 LoggedOutEvent,
825 )
826-from lp.services.webapp.metazcml import ILaunchpadPermission
827 from lp.services.webapp.publisher import LaunchpadView
828 from lp.services.webapp.url import urlappend
829 from lp.services.webapp.vhosts import allvhosts
830@@ -68,33 +66,9 @@
831 class UnauthorizedView(SystemErrorView):
832
833 response_code = None
834-
835- forbidden_page = ViewPageTemplateFile(
836- '../../../lp/app/templates/launchpad-forbidden.pt')
837-
838- read_only_page = ViewPageTemplateFile(
839- '../../../lp/app/templates/launchpad-readonlyfailure.pt')
840-
841- def page_title(self):
842- if is_read_only():
843- return super(UnauthorizedView, self).page_title
844- else:
845- return 'Forbidden'
846+ page_title = 'Forbidden'
847
848 def __call__(self):
849- # In read only mode, Unauthorized exceptions get raised by the
850- # security policy when write permissions are requested. We need
851- # to render the read-only failure screen so the user knows their
852- # request failed for operational reasons rather than a genuine
853- # permission problem.
854- if is_read_only():
855- # Our context is an Unauthorized exception, which acts like
856- # a tuple containing (object, attribute_requested, permission).
857- lp_permission = getUtility(ILaunchpadPermission, self.context[2])
858- if lp_permission.access_level != "read":
859- self.request.response.setStatus(503) # Service Unavailable
860- return self.read_only_page()
861-
862 if IUnauthenticatedPrincipal.providedBy(self.request.principal):
863 if 'loggingout' in self.request.form:
864 target = '%s?loggingout=1' % self.request.URL[-2]
865@@ -133,7 +107,7 @@
866 return ''
867 else:
868 self.request.response.setStatus(403) # Forbidden
869- return self.forbidden_page()
870+ return self.template()
871
872 def getRedirectURL(self, current_url, query_string):
873 """Get the URL to redirect to.
874@@ -569,4 +543,4 @@
875 assert IUnauthenticatedPrincipal.providedBy(self.request.principal), (
876 "Feeds user should always be anonymous.")
877 self.request.response.setStatus(403) # Forbidden
878- return self.forbidden_page()
879+ return self.template()
880
881=== modified file 'lib/lp/services/webapp/publication.py'
882--- lib/lp/services/webapp/publication.py 2012-01-04 05:07:53 +0000
883+++ lib/lp/services/webapp/publication.py 2012-08-09 04:24:19 +0000
884@@ -64,7 +64,6 @@
885 )
886 from lp.services import features
887 from lp.services.config import config
888-from lp.services.database.readonly import is_read_only
889 from lp.services.features.flags import NullFeatureController
890 from lp.services.oauth.interfaces import IOAuthSignedRequest
891 from lp.services.osutils import open_for_writing
892@@ -74,7 +73,6 @@
893 FinishReadOnlyRequestEvent,
894 IDatabasePolicy,
895 ILaunchpadRoot,
896- INotificationResponse,
897 IOpenLaunchBag,
898 IPlacelessAuthUtility,
899 IPrimaryContext,
900@@ -84,7 +82,6 @@
901 OffsiteFormPostError,
902 StartRequestEvent,
903 )
904-from lp.services.webapp.menu import structured
905 from lp.services.webapp.opstats import OpStats
906 from lp.services.webapp.vhosts import allvhosts
907
908@@ -267,32 +264,9 @@
909
910 transaction.begin()
911
912- db_policy = IDatabasePolicy(request)
913-
914- # If we have switched to or from read-only mode, we need to
915- # disconnect all Stores for this thread. We don't want the
916- # appserver to leave dangling connections as this will interfere
917- # with database maintenance.
918- # We don't disconnect Stores for threads currently handling
919- # requests. That would generate unreproducable OOPSes. This
920- # isn't a problem, as our requests should complete soon or
921- # timeout. Unfortunately, there is no way to disconnect Stores
922- # for idle threads. This means connections are left dangling
923- # until the appserver has processed as many requests as there
924- # are worker threads. We will be able to handle this better
925- # when we have a connection pool.
926- was_read_only = getattr(self.thread_locals, 'was_read_only', None)
927- if was_read_only is not None and was_read_only != is_read_only():
928- zstorm = getUtility(IZStorm)
929- for name, store in list(zstorm.iterstores()):
930- zstorm.remove(store)
931- store.close()
932- # is_read_only() is cached for the entire request, so there
933- # is no race condition here.
934- self.thread_locals.was_read_only = is_read_only()
935-
936 # Now we are logged in, install the correct IDatabasePolicy for
937 # this request.
938+ db_policy = IDatabasePolicy(request)
939 getUtility(IStoreSelector).push(db_policy)
940
941 getUtility(IOpenLaunchBag).clear()
942@@ -308,21 +282,6 @@
943 request.setPrincipal(principal)
944 self.maybeRestrictToTeam(request)
945 maybe_block_offsite_form_post(request)
946- self.maybeNotifyReadOnlyMode(request)
947-
948- def maybeNotifyReadOnlyMode(self, request):
949- """Hook to notify about read-only mode."""
950- if is_read_only():
951- notification_response = INotificationResponse(request, None)
952- if notification_response is not None:
953- notification_response.addWarningNotification(
954- structured("""
955- Launchpad is undergoing maintenance and is in
956- read-only mode. <i>You cannot make any
957- changes.</i> You can find more information on the
958- <a href="http://identi.ca/launchpadstatus">Launchpad
959- system status</a> page.
960- """))
961
962 def getPrincipal(self, request):
963 """Return the authenticated principal for this request.
964
965=== modified file 'lib/lp/services/webapp/tests/test_dbpolicy.py'
966--- lib/lp/services/webapp/tests/test_dbpolicy.py 2012-01-01 02:58:52 +0000
967+++ lib/lp/services/webapp/tests/test_dbpolicy.py 2012-08-09 04:24:19 +0000
968@@ -30,15 +30,10 @@
969 IMasterStore,
970 ISlaveStore,
971 )
972-from lp.services.database.tests.readonly import (
973- remove_read_only_file,
974- touch_read_only_file,
975- )
976 from lp.services.webapp.dbpolicy import (
977 BaseDatabasePolicy,
978 LaunchpadDatabasePolicy,
979 MasterDatabasePolicy,
980- ReadOnlyLaunchpadDatabasePolicy,
981 SlaveDatabasePolicy,
982 SlaveOnlyDatabasePolicy,
983 )
984@@ -50,7 +45,6 @@
985 IStoreSelector,
986 MAIN_STORE,
987 MASTER_FLAVOR,
988- ReadOnlyModeDisallowedStore,
989 SLAVE_FLAVOR,
990 )
991 from lp.services.webapp.servers import LaunchpadTestRequest
992@@ -230,63 +224,9 @@
993 finally:
994 endInteraction()
995
996- def test_WebServiceRequest_uses_ReadOnlyDatabasePolicy(self):
997- """WebService requests should use the read only database
998- policy in read only mode.
999- """
1000- touch_read_only_file()
1001- try:
1002- api_prefix = getUtility(
1003- IWebServiceConfiguration).active_versions[0]
1004- server_url = 'http://api.launchpad.dev/%s' % api_prefix
1005- request = LaunchpadTestRequest(SERVER_URL=server_url)
1006- setFirstLayer(request, WebServiceLayer)
1007- policy = IDatabasePolicy(request)
1008- self.assertIsInstance(policy, ReadOnlyLaunchpadDatabasePolicy)
1009- finally:
1010- remove_read_only_file()
1011-
1012- def test_read_only_mode_uses_ReadOnlyLaunchpadDatabasePolicy(self):
1013- touch_read_only_file()
1014- try:
1015- request = LaunchpadTestRequest(
1016- SERVER_URL='http://launchpad.dev')
1017- policy = IDatabasePolicy(request)
1018- self.assertIsInstance(policy, ReadOnlyLaunchpadDatabasePolicy)
1019- finally:
1020- remove_read_only_file()
1021-
1022 def test_other_request_uses_LaunchpadDatabasePolicy(self):
1023 """By default, requests should use the LaunchpadDatabasePolicy."""
1024 server_url = 'http://launchpad.dev/'
1025 request = LaunchpadTestRequest(SERVER_URL=server_url)
1026 policy = IDatabasePolicy(request)
1027 self.assertIsInstance(policy, LaunchpadDatabasePolicy)
1028-
1029-
1030-class ReadOnlyLaunchpadDatabasePolicyTestCase(BaseDatabasePolicyTestCase):
1031- """Tests for the `ReadOnlyModeLaunchpadDatabasePolicy`"""
1032-
1033- def setUp(self):
1034- self.policy = ReadOnlyLaunchpadDatabasePolicy()
1035- super(ReadOnlyLaunchpadDatabasePolicyTestCase, self).setUp()
1036-
1037- def test_defaults(self):
1038- # default Store is the slave.
1039- for store in ALL_STORES:
1040- self.assertProvides(
1041- getUtility(IStoreSelector).get(store, DEFAULT_FLAVOR),
1042- ISlaveStore)
1043-
1044- def test_slave_allowed(self):
1045- for store in ALL_STORES:
1046- self.assertProvides(
1047- getUtility(IStoreSelector).get(store, SLAVE_FLAVOR),
1048- ISlaveStore)
1049-
1050- def test_master_disallowed(self):
1051- store_selector = getUtility(IStoreSelector)
1052- for store in ALL_STORES:
1053- self.assertRaises(
1054- ReadOnlyModeDisallowedStore,
1055- store_selector.get, store, MASTER_FLAVOR)
1056
1057=== modified file 'lib/lp/services/webapp/tests/test_publication.py'
1058--- lib/lp/services/webapp/tests/test_publication.py 2012-01-01 02:58:52 +0000
1059+++ lib/lp/services/webapp/tests/test_publication.py 2012-08-09 04:24:19 +0000
1060@@ -5,7 +5,6 @@
1061
1062 __metaclass__ = type
1063
1064-import logging
1065 import sys
1066
1067 from contrib.oauth import (
1068@@ -17,7 +16,6 @@
1069 STATE_RECONNECT,
1070 )
1071 from storm.exceptions import DisconnectionError
1072-from storm.zope.interfaces import IZStorm
1073 from zope.component import getUtility
1074 from zope.interface import directlyProvides
1075 from zope.publisher.interfaces import (
1076@@ -25,13 +23,7 @@
1077 Retry,
1078 )
1079
1080-from lp.services.config import dbconfig
1081 from lp.services.database.lpstorm import IMasterStore
1082-from lp.services.database.readonly import is_read_only
1083-from lp.services.database.tests.readonly import (
1084- remove_read_only_file,
1085- touch_read_only_file,
1086- )
1087 from lp.services.identity.model.emailaddress import EmailAddress
1088 from lp.services.oauth.interfaces import (
1089 IOAuthConsumerSet,
1090@@ -39,13 +31,9 @@
1091 )
1092 import lp.services.webapp.adapter as dbadapter
1093 from lp.services.webapp.interfaces import (
1094- IStoreSelector,
1095- MAIN_STORE,
1096- MASTER_FLAVOR,
1097 NoReferrerError,
1098 OAuthPermission,
1099 OffsiteFormPostError,
1100- SLAVE_FLAVOR,
1101 )
1102 from lp.services.webapp.publication import (
1103 is_browser,
1104@@ -64,10 +52,7 @@
1105 TestCase,
1106 TestCaseWithFactory,
1107 )
1108-from lp.testing.layers import (
1109- DatabaseFunctionalLayer,
1110- FunctionalLayer,
1111- )
1112+from lp.testing.layers import DatabaseFunctionalLayer
1113
1114
1115 class TestLaunchpadBrowserPublication(TestCase):
1116@@ -95,137 +80,6 @@
1117 self.assertEquals(request.traversed_objects, [obj1])
1118
1119
1120-class TestReadOnlyModeSwitches(TestCase):
1121- # At the beginning of every request (in publication.beforeTraversal()), we
1122- # check to see if we've changed from/to read-only/read-write and if there
1123- # was a change we remove the main_master/slave stores from ZStorm, forcing
1124- # them to be recreated the next time they're needed, thus causing them to
1125- # point to the correct databases.
1126- layer = DatabaseFunctionalLayer
1127-
1128- def tearDown(self):
1129- TestCase.tearDown(self)
1130- # If a DB policy was installed (e.g. by publication.beforeTraversal),
1131- # uninstall it.
1132- try:
1133- getUtility(IStoreSelector).pop()
1134- except IndexError:
1135- pass
1136- # Cleanup needed so that further tests can start processing other
1137- # requests (e.g. calling beforeTraversal).
1138- self.publication.endRequest(self.request, None)
1139- # Force pending mode switches to actually happen and get logged so
1140- # that we don't interfere with other tests.
1141- assert not is_read_only(), (
1142- "A test failed to clean things up properly, leaving the app "
1143- "in read-only mode.")
1144-
1145- def setUp(self):
1146- TestCase.setUp(self)
1147- # Get the main_master/slave stores just to make sure they're added to
1148- # ZStorm.
1149- master = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
1150- slave = getUtility(IStoreSelector).get(MAIN_STORE, SLAVE_FLAVOR)
1151- self.master_connection = master._connection
1152- self.slave_connection = slave._connection
1153- self.zstorm = getUtility(IZStorm)
1154- self.publication = LaunchpadBrowserPublication(None)
1155- # Run through once to initialize. beforeTraversal will never
1156- # disconnect Stores the first run through because there is no
1157- # need.
1158- request = LaunchpadTestRequest()
1159- self.publication.beforeTraversal(request)
1160- self.publication.endRequest(request, None)
1161- getUtility(IStoreSelector).pop()
1162-
1163- self.request = LaunchpadTestRequest()
1164-
1165- @property
1166- def zstorm_stores(self):
1167- return [name for (name, store) in self.zstorm.iterstores()]
1168-
1169- def test_no_mode_changes(self):
1170- # Make sure the master/slave stores are present in zstorm.
1171- self.assertIn('main-master', self.zstorm_stores)
1172- self.assertIn('main-slave', self.zstorm_stores)
1173-
1174- self.publication.beforeTraversal(self.request)
1175-
1176- # Since the mode didn't change, the stores were left in zstorm.
1177- self.assertIn('main-master', self.zstorm_stores)
1178- self.assertIn('main-slave', self.zstorm_stores)
1179-
1180- # With the store's connection being the same as before.
1181- master = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
1182- self.assertIs(self.master_connection, master._connection)
1183-
1184- # And they still point to the read-write databases.
1185- self.assertEquals(
1186- dbconfig.rw_main_master.strip(),
1187- # XXX: 2009-01-12, salgado, bug=506536: We shouldn't need to go
1188- # through private attributes to get to the store's database.
1189- master._connection._database.dsn_without_user.strip())
1190-
1191- def test_changing_modes(self):
1192- # Make sure the master/slave stores are present in zstorm.
1193- self.assertIn('main-master', self.zstorm_stores)
1194- self.assertIn('main-slave', self.zstorm_stores)
1195-
1196- try:
1197- touch_read_only_file()
1198- self.publication.beforeTraversal(self.request)
1199- finally:
1200- # Tell remove_read_only_file() to not assert that the mode switch
1201- # actually happened, as we know it won't happen until this request
1202- # is finished.
1203- remove_read_only_file(assert_mode_switch=False)
1204-
1205- # Here the mode has changed to read-only, so the stores were removed
1206- # from zstorm.
1207- self.assertNotIn('main-master', self.zstorm_stores)
1208- self.assertNotIn('main-slave', self.zstorm_stores)
1209-
1210- # If they're needed again, they'll be re-created by ZStorm, and when
1211- # that happens they will point to the read-only databases.
1212- master = getUtility(IStoreSelector).get(MAIN_STORE, SLAVE_FLAVOR)
1213- self.assertEquals(
1214- dbconfig.ro_main_master.strip(),
1215- # XXX: 2009-01-12, salgado, bug=506536: We shouldn't need to go
1216- # through private attributes to get to the store's database.
1217- master._connection._database.dsn_without_user.strip())
1218-
1219-
1220-class TestReadOnlyNotifications(TestCase):
1221- """Tests for `LaunchpadBrowserPublication.maybeNotifyReadOnlyMode`."""
1222-
1223- layer = FunctionalLayer
1224-
1225- def setUp(self):
1226- TestCase.setUp(self)
1227- touch_read_only_file()
1228- self.addCleanup(remove_read_only_file, assert_mode_switch=False)
1229-
1230- def test_notification(self):
1231- # In read-only mode, maybeNotifyReadOnlyMode adds a warning that
1232- # changes cannot be made to every request that supports notifications.
1233- publication = LaunchpadBrowserPublication(None)
1234- request = LaunchpadTestRequest()
1235- publication.maybeNotifyReadOnlyMode(request)
1236- self.assertEqual(1, len(request.notifications))
1237- notification = request.notifications[0]
1238- self.assertEqual(logging.WARNING, notification.level)
1239- self.assertTrue('read-only mode' in notification.message)
1240-
1241- def test_notification_xmlrpc(self):
1242- # Even in read-only mode, maybeNotifyReadOnlyMode doesn't try to add a
1243- # notification to a request that doesn't support notifications.
1244- from lp.services.webapp.servers import PublicXMLRPCRequest
1245- publication = LaunchpadBrowserPublication(None)
1246- request = PublicXMLRPCRequest(None, {})
1247- # This is just assertNotRaises
1248- publication.maybeNotifyReadOnlyMode(request)
1249-
1250-
1251 class TestWebServicePublication(TestCaseWithFactory):
1252 layer = DatabaseFunctionalLayer
1253
1254
1255=== modified file 'lib/lp/translations/browser/serieslanguage.py'
1256--- lib/lp/translations/browser/serieslanguage.py 2012-01-01 02:58:52 +0000
1257+++ lib/lp/translations/browser/serieslanguage.py 2012-08-09 04:24:19 +0000
1258@@ -15,7 +15,6 @@
1259 from lp.app.browser.tales import PersonFormatterAPI
1260 from lp.registry.model.sourcepackagename import SourcePackageName
1261 from lp.services.database.bulk import load_related
1262-from lp.services.database.readonly import is_read_only
1263 from lp.services.propertycache import cachedproperty
1264 from lp.services.webapp import LaunchpadView
1265 from lp.services.webapp.batching import BatchNavigator
1266@@ -85,12 +84,6 @@
1267 @property
1268 def access_level_description(self):
1269 """Must not be called when there's no translation group."""
1270-
1271- if is_read_only():
1272- return (
1273- "No work can be done on these translations while Launchpad "
1274- "is in read-only mode.")
1275-
1276 if self.user is None:
1277 return ("You are not logged in. Please log in to work "
1278 "on translations.")
1279
1280=== modified file 'lib/lp/translations/browser/tests/test_distroserieslanguage_views.py'
1281--- lib/lp/translations/browser/tests/test_distroserieslanguage_views.py 2012-01-01 02:58:52 +0000
1282+++ lib/lp/translations/browser/tests/test_distroserieslanguage_views.py 2012-08-09 04:24:19 +0000
1283@@ -3,7 +3,6 @@
1284
1285 __metaclass__ = type
1286
1287-from lazr.restful.utils import get_current_browser_request
1288 from testtools.matchers import Equals
1289 import transaction
1290 from zope.component import getUtility
1291@@ -40,11 +39,6 @@
1292 self.view = DistroSeriesLanguageView(
1293 self.dsl, LaunchpadTestRequest())
1294
1295- def _simulateReadOnlyMode(self):
1296- """Pretend to be in read-only mode for this test."""
1297- request = get_current_browser_request()
1298- request.annotations['launchpad.read_only_mode'] = True
1299-
1300 def test_empty_view(self):
1301 self.assertEquals(self.view.translation_group, None)
1302 self.assertEquals(self.view.translation_team, None)
1303@@ -78,13 +72,6 @@
1304 self.view.initialize()
1305 self.assertEquals(self.view.translation_team, translator)
1306
1307- def test_access_level_description_handles_readonly(self):
1308- self._simulateReadOnlyMode()
1309- notice = (
1310- "No work can be done on these translations while Launchpad "
1311- "is in read-only mode.")
1312- self.assertEqual(notice, self.view.access_level_description)
1313-
1314 def test_sourcepackagenames_bulk_loaded(self):
1315 # SourcePackageName records referenced by POTemplates
1316 # are bulk loaded. Accessing the sourcepackagename attribute
1317
1318=== modified file 'lib/lp/translations/browser/translationmessage.py'
1319--- lib/lp/translations/browser/translationmessage.py 2012-06-29 08:40:05 +0000
1320+++ lib/lp/translations/browser/translationmessage.py 2012-08-09 04:24:19 +0000
1321@@ -39,7 +39,6 @@
1322 from zope.schema.vocabulary import getVocabularyRegistry
1323
1324 from lp.app.errors import UnexpectedFormData
1325-from lp.services.database.readonly import is_read_only
1326 from lp.services.propertycache import cachedproperty
1327 from lp.services.webapp import (
1328 ApplicationMenu,
1329@@ -382,11 +381,6 @@
1330 principle the user should not have been given the option to
1331 submit the current request.
1332 """
1333- if is_read_only():
1334- raise UnexpectedFormData(
1335- "Launchpad is currently in read-only mode for maintenance. "
1336- "Please try again later.")
1337-
1338 if self.user is None:
1339 raise UnexpectedFormData("You are not logged in.")
1340
1341
1342=== modified file 'lib/lp/translations/model/pofile.py'
1343--- lib/lp/translations/model/pofile.py 2012-06-29 08:40:05 +0000
1344+++ lib/lp/translations/model/pofile.py 2012-08-09 04:24:19 +0000
1345@@ -52,7 +52,6 @@
1346 from lp.services.database.constants import UTC_NOW
1347 from lp.services.database.datetimecol import UtcDateTimeCol
1348 from lp.services.database.lpstorm import IStore
1349-from lp.services.database.readonly import is_read_only
1350 from lp.services.database.sqlbase import (
1351 flush_database_updates,
1352 quote,
1353@@ -153,17 +152,11 @@
1354
1355 def canEditTranslations(self, person):
1356 """See `IPOFile`."""
1357- if is_read_only():
1358- # Nothing can be edited in read-only mode.
1359- return False
1360 policy = self.potemplate.getTranslationPolicy()
1361 return policy.allowsTranslationEdits(person, self.language)
1362
1363 def canAddSuggestions(self, person):
1364 """See `IPOFile`."""
1365- if is_read_only():
1366- # No data can be entered in read-only mode.
1367- return False
1368 policy = self.potemplate.getTranslationPolicy()
1369 return policy.allowsTranslationSuggestions(person, self.language)
1370