Merge lp:~cjwatson/launchpad/webservice-commit-feature-flag into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18515
Proposed branch: lp:~cjwatson/launchpad/webservice-commit-feature-flag
Merge into: lp:launchpad
Diff against target: 126 lines (+46/-16)
2 files modified
lib/lp/services/webapp/servers.py (+20/-13)
lib/lp/services/webapp/tests/test_servers.py (+26/-3)
To merge this branch: bzr merge lp:~cjwatson/launchpad/webservice-commit-feature-flag
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+335287@code.launchpad.net

Commit message

Disable the post-webservice-GET commit if the webservice.read_only_commit.disabled feature flag is set.

Description of the change

I've had a quick look through the current set of @export_read_operation methods and haven't found any obvious cases that would cause trouble, but I suppose we'll see.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
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/services/webapp/servers.py'
2--- lib/lp/services/webapp/servers.py 2017-09-16 13:31:57 +0000
3+++ lib/lp/services/webapp/servers.py 2017-12-17 01:39:00 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Definition of the internet servers that Launchpad uses."""
10@@ -64,7 +64,10 @@
11 from lp.app.errors import UnexpectedFormData
12 import lp.layers
13 from lp.services.config import config
14-from lp.services.features import get_relevant_feature_controller
15+from lp.services.features import (
16+ get_relevant_feature_controller,
17+ getFeatureFlag,
18+ )
19 from lp.services.features.flags import NullFeatureController
20 from lp.services.feeds.interfaces.application import IFeedsApplication
21 from lp.services.feeds.interfaces.feed import IFeed
22@@ -1240,17 +1243,21 @@
23
24 def finishReadOnlyRequest(self, request, ob, txn):
25 """Commit the transaction even though there should be no writes."""
26- # WebServicePublication used to commit on every request to store
27- # OAuthNonces to prevent replay attacks. But TLS prevents replay
28- # attacks too, so we don't bother with nonces any more. However,
29- # this commit will stay here until we can switch it off in a
30- # controlled test to ensure that nothing depends on it on prod.
31- notify(FinishReadOnlyRequestEvent(ob, request))
32- # Transaction commits usually need to be aware of the possibility of
33- # a doomed transaction. We do not expect that this code will
34- # encounter doomed transactions. If it does, this will need to be
35- # revisited.
36- txn.commit()
37+ if getFeatureFlag('webservice.read_only_commit.disabled'):
38+ super(WebServicePublication, self).finishReadOnlyRequest(
39+ request, ob, txn)
40+ else:
41+ # WebServicePublication used to commit on every request to store
42+ # OAuthNonces to prevent replay attacks. But TLS prevents replay
43+ # attacks too, so we don't bother with nonces any more. However,
44+ # this commit will stay here until we can switch it off in a
45+ # controlled test to ensure that nothing depends on it on prod.
46+ notify(FinishReadOnlyRequestEvent(ob, request))
47+ # Transaction commits usually need to be aware of the
48+ # possibility of a doomed transaction. We do not expect that
49+ # this code will encounter doomed transactions. If it does,
50+ # this will need to be revisited.
51+ txn.commit()
52
53 def getPrincipal(self, request):
54 """See `LaunchpadBrowserPublication`.
55
56=== modified file 'lib/lp/services/webapp/tests/test_servers.py'
57--- lib/lp/services/webapp/tests/test_servers.py 2016-09-14 11:13:06 +0000
58+++ lib/lp/services/webapp/tests/test_servers.py 2017-12-17 01:39:00 +0000
59@@ -1,4 +1,4 @@
60-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
61+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
62 # GNU Affero General Public License version 3 (see the file LICENSE).
63
64 __metaclass__ = type
65@@ -31,6 +31,7 @@
66 )
67
68 from lp.app import versioninfo
69+from lp.services.features.testing import FeatureFixture
70 from lp.services.webapp.interfaces import IFinishReadOnlyRequestEvent
71 from lp.services.webapp.publication import LaunchpadBrowserPublication
72 from lp.services.webapp.servers import (
73@@ -52,7 +53,10 @@
74 EventRecorder,
75 TestCase,
76 )
77-from lp.testing.layers import FunctionalLayer
78+from lp.testing.layers import (
79+ FunctionalLayer,
80+ ZopelessDatabaseLayer,
81+ )
82
83
84 class SetInWSGIEnvironmentTestCase(TestCase):
85@@ -694,7 +698,7 @@
86 self.log.append("ABORT")
87
88
89-class TestFinishReadOnlyRequest(TestCase):
90+class TestFinishReadOnlyRequestMixin:
91 # Publications that have a finishReadOnlyRequest() method are obliged to
92 # fire an IFinishReadOnlyRequestEvent.
93
94@@ -725,6 +729,8 @@
95 self.assertIs(fake_request, finish_event.request)
96 self.assertIs(fake_object, finish_event.object)
97
98+class TestFinishReadOnlyRequest(TestFinishReadOnlyRequestMixin, TestCase):
99+
100 def test_WebServicePub_fires_FinishReadOnlyRequestEvent(self):
101 # WebServicePublication.finishReadOnlyRequest() issues an
102 # IFinishReadOnlyRequestEvent and commits the transaction.
103@@ -738,6 +744,23 @@
104 self._test_publication(publication, ["ABORT"])
105
106
107+class TestFinishReadOnlyRequestFeatureFlag(
108+ TestFinishReadOnlyRequestMixin, TestCase):
109+
110+ layer = ZopelessDatabaseLayer
111+
112+ def test_WebServicePub_aborts_if_read_only_commit_disabled(self):
113+ # If the webservice.read_only_commit.disabled feature flag is set,
114+ # WebServicePublication.finishReadOnlyRequest() behaves like
115+ # LaunchpadBrowserPublication.finishReadOnlyRequest() and aborts the
116+ # transaction. (This is intended to be the default behaviour in
117+ # future.)
118+ self.useFixture(FeatureFixture(
119+ {'webservice.read_only_commit.disabled': 'on'}))
120+ publication = WebServicePublication(None)
121+ self._test_publication(publication, ["ABORT"])
122+
123+
124 def test_suite():
125 suite = unittest.TestSuite()
126 suite.addTest(DocTestSuite(