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
=== modified file 'lib/lp/services/webapp/servers.py'
--- lib/lp/services/webapp/servers.py 2017-09-16 13:31:57 +0000
+++ lib/lp/services/webapp/servers.py 2017-12-17 01:39:00 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the1# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Definition of the internet servers that Launchpad uses."""4"""Definition of the internet servers that Launchpad uses."""
@@ -64,7 +64,10 @@
64from lp.app.errors import UnexpectedFormData64from lp.app.errors import UnexpectedFormData
65import lp.layers65import lp.layers
66from lp.services.config import config66from lp.services.config import config
67from lp.services.features import get_relevant_feature_controller67from lp.services.features import (
68 get_relevant_feature_controller,
69 getFeatureFlag,
70 )
68from lp.services.features.flags import NullFeatureController71from lp.services.features.flags import NullFeatureController
69from lp.services.feeds.interfaces.application import IFeedsApplication72from lp.services.feeds.interfaces.application import IFeedsApplication
70from lp.services.feeds.interfaces.feed import IFeed73from lp.services.feeds.interfaces.feed import IFeed
@@ -1240,17 +1243,21 @@
12401243
1241 def finishReadOnlyRequest(self, request, ob, txn):1244 def finishReadOnlyRequest(self, request, ob, txn):
1242 """Commit the transaction even though there should be no writes."""1245 """Commit the transaction even though there should be no writes."""
1243 # WebServicePublication used to commit on every request to store1246 if getFeatureFlag('webservice.read_only_commit.disabled'):
1244 # OAuthNonces to prevent replay attacks. But TLS prevents replay1247 super(WebServicePublication, self).finishReadOnlyRequest(
1245 # attacks too, so we don't bother with nonces any more. However,1248 request, ob, txn)
1246 # this commit will stay here until we can switch it off in a1249 else:
1247 # controlled test to ensure that nothing depends on it on prod.1250 # WebServicePublication used to commit on every request to store
1248 notify(FinishReadOnlyRequestEvent(ob, request))1251 # OAuthNonces to prevent replay attacks. But TLS prevents replay
1249 # Transaction commits usually need to be aware of the possibility of1252 # attacks too, so we don't bother with nonces any more. However,
1250 # a doomed transaction. We do not expect that this code will1253 # this commit will stay here until we can switch it off in a
1251 # encounter doomed transactions. If it does, this will need to be1254 # controlled test to ensure that nothing depends on it on prod.
1252 # revisited.1255 notify(FinishReadOnlyRequestEvent(ob, request))
1253 txn.commit()1256 # Transaction commits usually need to be aware of the
1257 # possibility of a doomed transaction. We do not expect that
1258 # this code will encounter doomed transactions. If it does,
1259 # this will need to be revisited.
1260 txn.commit()
12541261
1255 def getPrincipal(self, request):1262 def getPrincipal(self, request):
1256 """See `LaunchpadBrowserPublication`.1263 """See `LaunchpadBrowserPublication`.
12571264
=== modified file 'lib/lp/services/webapp/tests/test_servers.py'
--- lib/lp/services/webapp/tests/test_servers.py 2016-09-14 11:13:06 +0000
+++ lib/lp/services/webapp/tests/test_servers.py 2017-12-17 01:39:00 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the1# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -31,6 +31,7 @@
31 )31 )
3232
33from lp.app import versioninfo33from lp.app import versioninfo
34from lp.services.features.testing import FeatureFixture
34from lp.services.webapp.interfaces import IFinishReadOnlyRequestEvent35from lp.services.webapp.interfaces import IFinishReadOnlyRequestEvent
35from lp.services.webapp.publication import LaunchpadBrowserPublication36from lp.services.webapp.publication import LaunchpadBrowserPublication
36from lp.services.webapp.servers import (37from lp.services.webapp.servers import (
@@ -52,7 +53,10 @@
52 EventRecorder,53 EventRecorder,
53 TestCase,54 TestCase,
54 )55 )
55from lp.testing.layers import FunctionalLayer56from lp.testing.layers import (
57 FunctionalLayer,
58 ZopelessDatabaseLayer,
59 )
5660
5761
58class SetInWSGIEnvironmentTestCase(TestCase):62class SetInWSGIEnvironmentTestCase(TestCase):
@@ -694,7 +698,7 @@
694 self.log.append("ABORT")698 self.log.append("ABORT")
695699
696700
697class TestFinishReadOnlyRequest(TestCase):701class TestFinishReadOnlyRequestMixin:
698 # Publications that have a finishReadOnlyRequest() method are obliged to702 # Publications that have a finishReadOnlyRequest() method are obliged to
699 # fire an IFinishReadOnlyRequestEvent.703 # fire an IFinishReadOnlyRequestEvent.
700704
@@ -725,6 +729,8 @@
725 self.assertIs(fake_request, finish_event.request)729 self.assertIs(fake_request, finish_event.request)
726 self.assertIs(fake_object, finish_event.object)730 self.assertIs(fake_object, finish_event.object)
727731
732class TestFinishReadOnlyRequest(TestFinishReadOnlyRequestMixin, TestCase):
733
728 def test_WebServicePub_fires_FinishReadOnlyRequestEvent(self):734 def test_WebServicePub_fires_FinishReadOnlyRequestEvent(self):
729 # WebServicePublication.finishReadOnlyRequest() issues an735 # WebServicePublication.finishReadOnlyRequest() issues an
730 # IFinishReadOnlyRequestEvent and commits the transaction.736 # IFinishReadOnlyRequestEvent and commits the transaction.
@@ -738,6 +744,23 @@
738 self._test_publication(publication, ["ABORT"])744 self._test_publication(publication, ["ABORT"])
739745
740746
747class TestFinishReadOnlyRequestFeatureFlag(
748 TestFinishReadOnlyRequestMixin, TestCase):
749
750 layer = ZopelessDatabaseLayer
751
752 def test_WebServicePub_aborts_if_read_only_commit_disabled(self):
753 # If the webservice.read_only_commit.disabled feature flag is set,
754 # WebServicePublication.finishReadOnlyRequest() behaves like
755 # LaunchpadBrowserPublication.finishReadOnlyRequest() and aborts the
756 # transaction. (This is intended to be the default behaviour in
757 # future.)
758 self.useFixture(FeatureFixture(
759 {'webservice.read_only_commit.disabled': 'on'}))
760 publication = WebServicePublication(None)
761 self._test_publication(publication, ["ABORT"])
762
763
741def test_suite():764def test_suite():
742 suite = unittest.TestSuite()765 suite = unittest.TestSuite()
743 suite.addTest(DocTestSuite(766 suite.addTest(DocTestSuite(