Merge lp:~sinzui/launchpad/registry-cache-3 into lp:launchpad

Proposed by Curtis Hovey
Status: Merged
Approved by: Edwin Grubbs
Approved revision: no longer in the source branch.
Merged at revision: 11087
Proposed branch: lp:~sinzui/launchpad/registry-cache-3
Merge into: lp:launchpad
Diff against target: 283 lines (+142/-54)
6 files modified
lib/lp/registry/browser/tests/milestone-views.txt (+1/-50)
lib/lp/registry/browser/tests/test_milestone.py (+64/-0)
lib/lp/registry/browser/tests/test_packaging.py (+35/-0)
lib/lp/registry/templates/distroseries-needs-packaging.pt (+3/-2)
lib/lp/registry/templates/distroseries-packaging.pt (+3/-2)
lib/lp/testing/memcache.py (+36/-0)
To merge this branch: bzr merge lp:~sinzui/launchpad/registry-cache-3
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) Approve
Review via email: mp+28931@code.launchpad.net

Description of the change

This is my branch to fix the +packaging and +needs-packaging cache rule.

    lp:~sinzui/launchpad/registry-cache-3
    Diff size: 284
    Launchpad bug:
          https://bugs.launchpad.net/bugs/599818
    Test command: ./bin/test -vv \
          -t packaging-views \
          -t xx-sourcepackage-packaging -t xx-show-distroseries-packaging
    Pre-implementation: no one.
    Target release: 10.06

Fix the +packaging and +needs-packaging cache rule
--------------------------------------------------

The pages still timeout on edge and staging even though the caching was added.
The cache rule was applied to the repeat statement, which means the view
always calls the expensive query, then uses the cache for the item.

Rules
-----

    * Move the cache rule before the call to `batch` to ensure the entire
      listing is cached. The batch will be called again when the cache
      expires.
    * ADDENDUM: Add a memcache test helper so that it is easy to verify
      that a fragment is in the cache chunk.

QA
--

    * Visit https://edge.launchpad.net/ubuntu/maverick/+packaging
    * Load the page (the test does not start until it is fully loaded)
    * Reload the page 10 times and verify there are no timeouts.
    * Visit https://edge.launchpad.net/ubuntu/maverick/+needs-packaging
    * Load the page (the test does not start until it is fully loaded)
    * Reload the page 10 times and verify there are no timeouts.

Lint
----

Linting changed files:
  lib/lp/registry/browser/tests/milestone-views.txt
  lib/lp/registry/browser/tests/test_milestone.py
  lib/lp/registry/browser/tests/test_packaging.py
  lib/lp/registry/templates/distroseries-needs-packaging.pt
  lib/lp/registry/templates/distroseries-packaging.pt
  lib/lp/testing/memcache.py

Test
----

    * lib/lp/testing/memcache.py
      * Added a test fixture for memcache testing.
    * lib/lp/registry/browser/tests/test_packaging.py
      * Added tests to verify the whole table is in the cache chunk for
        +packaging and +needs-packaging.
    * lib/lp/registry/browser/tests/milestone-views.txt
      * Moved cache tests to test_milestone.py
    * lib/lp/registry/browser/tests/test_milestone.py
      * Updated tests to use the new memcache unit test fixture.

Implementation
--------------

    * lib/lp/registry/templates/distroseries-needs-packaging.pt
      * Move cache rule to the whole table to ensure batch is not called
        if there is a cache hit.
    * lib/lp/registry/templates/distroseries-packaging.pt
      * Move cache rule to the whole table to ensure batch is not called
        if there is a cache hit.

To post a comment you must log in.
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Hi Curtis,

This branch looks good.

-Edwin

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/browser/tests/milestone-views.txt'
2--- lib/lp/registry/browser/tests/milestone-views.txt 2010-06-29 14:52:51 +0000
3+++ lib/lp/registry/browser/tests/milestone-views.txt 2010-06-30 20:05:55 +0000
4@@ -248,7 +248,7 @@
5 the product release data for a milestone.
6
7 >>> from canonical.launchpad.testing.pages import (
8- ... extract_text, find_main_content, find_tag_by_id)
9+ ... extract_text, find_tag_by_id)
10
11 >>> view = create_view(
12 ... milestone, '+productrelease-data', principal=person)
13@@ -301,55 +301,6 @@
14 >>> login_person(person)
15
16
17-Milestone caching
18------------------
19-
20-Active milestones cache bugtasks for 10 minutes.
21-
22- >>> from canonical.testing.layers import MemcachedLayer
23-
24- >>> MemcachedLayer.purge()
25- >>> cached_milestone = factory.makeMilestone(
26- ... 'cache-test', productseries=series)
27- >>> cached_bugtask = factory.makeBugTask(target=cached_milestone.product)
28- >>> cached_bugtask.milestone = cached_milestone
29- >>> view = create_view(cached_milestone, '+index', principal=person)
30- >>> view.expire_cache_minutes
31- 10
32-
33- >>> 'Cache hit' in view.render()
34- False
35-
36- >>> content = find_main_content(create_view(
37- ... cached_milestone, '+index', principal=person).render())
38- >>> print find_tag_by_id(content, 'milestone-activities')
39- <div id="milestone-activities" ...<!--
40- Cache hit: memcache expression
41- (private, view/expire_cache_minutes minute) [600 seconds] ...
42- <dt>Assigned to you:</dt> ... No blueprints or bugs assigned to you. ...
43-
44- >>> print find_tag_by_id(content, 'milestone-specs-bugs')
45- <div id="milestone-specs-bugs" ...
46- <!-- Cache hit: memcache expression
47- (private, view/expire_cache_minutes minute) [600 seconds] -->
48- <table class="listing sortable" id="milestone_bugtasks">...
49-
50-Inactive milestones cache bugtasks for 3 hours.
51-
52- >>> MemcachedLayer.purge()
53- >>> cached_milestone.active = False
54- >>> view = create_view(cached_milestone, '+index', principal=person)
55- >>> view.expire_cache_minutes
56- 360
57- >>> 'Cache hit' in view.render()
58- False
59- >>> print create_view(
60- ... cached_milestone, '+index', principal=person).render()
61- <...<!-- Cache hit: memcache expression
62- (private, view/expire_cache_minutes minute) [21600 seconds] -->
63- <table class="listing sortable" id="milestone_bugtasks">...
64-
65-
66 ProjectGroup milestones
67 -----------------------
68
69
70=== added file 'lib/lp/registry/browser/tests/test_milestone.py'
71--- lib/lp/registry/browser/tests/test_milestone.py 1970-01-01 00:00:00 +0000
72+++ lib/lp/registry/browser/tests/test_milestone.py 2010-06-30 20:05:55 +0000
73@@ -0,0 +1,64 @@
74+# Copyright 2010 Canonical Ltd. This software is licensed under the
75+# GNU Affero General Public License version 3 (see the file LICENSE).
76+
77+"""Test milestone views."""
78+
79+__metaclass__ = type
80+
81+import unittest
82+
83+from lp.testing import login_person
84+from lp.testing.views import create_initialized_view
85+from lp.testing.memcache import MemcacheTestCase
86+
87+
88+class TestMilestoneMemcache(MemcacheTestCase):
89+
90+ def setUp(self):
91+ super(TestMilestoneMemcache, self).setUp()
92+ product = self.factory.makeProduct()
93+ login_person(product.owner)
94+ series = self.factory.makeProductSeries(product=product)
95+ self.milestone = self.factory.makeMilestone(
96+ productseries=series, name="1.1")
97+ bugtask = self.factory.makeBugTask(target=product)
98+ bugtask.transitionToAssignee(product.owner)
99+ bugtask.milestone = self.milestone
100+ self.observer = self.factory.makePerson()
101+
102+ def test_milestone_index_memcache(self):
103+ # Miss the cache on first render.
104+ view = create_initialized_view(
105+ self.milestone, name='+index', principal=self.observer)
106+ content = view.render()
107+ self.assertCacheMiss('<dt>Assigned to you:</dt>', content)
108+ self.assertCacheMiss('id="milestone_bugtasks"', content)
109+ # Hit the cache on the second render.
110+ view = create_initialized_view(
111+ self.milestone, name='+index', principal=self.observer)
112+ self.assertTrue(view.milestone.active)
113+ self.assertEqual(10, view.expire_cache_minutes)
114+ content = view.render()
115+ self.assertCacheHit(
116+ '<dt>Assigned to you:</dt>',
117+ 'private, view/expire_cache_minutes minute', content)
118+ self.assertCacheHit(
119+ 'id="milestone_bugtasks"',
120+ 'private, view/expire_cache_minutes minute', content)
121+
122+ def test_milestone_index_active_cache_time(self):
123+ # Verify the active milestone cache time.
124+ view = create_initialized_view(self.milestone, name='+index')
125+ self.assertTrue(view.milestone.active)
126+ self.assertEqual(10, view.expire_cache_minutes)
127+
128+ def test_milestone_index_inactive_cache_time(self):
129+ # Verify the inactive milestone cache time.
130+ self.milestone.active = False
131+ view = create_initialized_view(self.milestone, name='+index')
132+ self.assertFalse(view.milestone.active)
133+ self.assertEqual(360, view.expire_cache_minutes)
134+
135+
136+def test_suite():
137+ return unittest.TestLoader().loadTestsFromName(__name__)
138
139=== modified file 'lib/lp/registry/browser/tests/test_packaging.py'
140--- lib/lp/registry/browser/tests/test_packaging.py 2010-04-16 19:28:24 +0000
141+++ lib/lp/registry/browser/tests/test_packaging.py 2010-06-30 20:05:55 +0000
142@@ -15,6 +15,7 @@
143 from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
144 from lp.testing import TestCaseWithFactory
145 from lp.testing.views import create_initialized_view
146+from lp.testing.memcache import MemcacheTestCase
147 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
148 from canonical.launchpad.ftests import login, logout
149 from canonical.launchpad.testing.pages import setupBrowser
150@@ -165,5 +166,39 @@
151 distroseries=distroseries))
152
153
154+class TestDistroseriesPackagingMemcache(MemcacheTestCase):
155+ """Tests distroseries packaging cache rules."""
156+
157+ def setUp(self):
158+ super(TestDistroseriesPackagingMemcache, self).setUp()
159+ ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
160+ self.hoary = ubuntu.getSeries('hoary')
161+ self.observer = self.factory.makePerson()
162+
163+ def test_needs_packaging_memcache(self):
164+ # Verify that the packages table is cached.
165+ # Miss the cache on first render.
166+ view = create_initialized_view(
167+ self.hoary, name='+needs-packaging', principal=self.observer)
168+ self.assertCacheMiss('<table id="packages"', view.render())
169+ # Hit the cache on the second render.
170+ view = create_initialized_view(
171+ self.hoary, name='+needs-packaging', principal=self.observer)
172+ self.assertCacheHit(
173+ '<table id="packages"', 'public, 30 minute', view.render())
174+
175+ def test_packaging_memcache(self):
176+ # Verify that the packagings table is cached.
177+ # Miss the cache on first render.
178+ view = create_initialized_view(
179+ self.hoary, name='+packaging', principal=self.observer)
180+ self.assertCacheMiss('<table id="packagings"', view.render())
181+ # Hit the cache on the second render.
182+ view = create_initialized_view(
183+ self.hoary, name='+packaging', principal=self.observer)
184+ self.assertCacheHit(
185+ '<table id="packagings"', 'public, 30 minute', view.render())
186+
187+
188 def test_suite():
189 return TestLoader().loadTestsFromName(__name__)
190
191=== modified file 'lib/lp/registry/templates/distroseries-needs-packaging.pt'
192--- lib/lp/registry/templates/distroseries-needs-packaging.pt 2010-06-29 15:03:03 +0000
193+++ lib/lp/registry/templates/distroseries-needs-packaging.pt 2010-06-30 20:05:55 +0000
194@@ -31,6 +31,7 @@
195 </ul>
196 </div>
197
198+ <tal:cache content="cache:public, 30 minute">
199 <div class="narrow"
200 tal:define="summaries view/cached_unlinked_packages;"
201 tal:condition="summaries/batch">
202@@ -47,8 +48,7 @@
203 </thead>
204
205 <tbody>
206- <tr tal:repeat="summary summaries/batch"
207- tal:content="cache:public, 30 minute">
208+ <tr tal:repeat="summary summaries/batch">
209 <td>
210 <a class="sprite package-source"
211 tal:attributes="href summary/package/fmt:url"
212@@ -87,6 +87,7 @@
213 <div class="results"
214 tal:content="structure summaries/@@+navigation-links-lower" />
215 </div>
216+ </tal:cache>
217 </div>
218 </body>
219 </html>
220
221=== modified file 'lib/lp/registry/templates/distroseries-packaging.pt'
222--- lib/lp/registry/templates/distroseries-packaging.pt 2010-06-29 15:03:03 +0000
223+++ lib/lp/registry/templates/distroseries-packaging.pt 2010-06-30 20:05:55 +0000
224@@ -31,8 +31,8 @@
225 </ul>
226 </div>
227
228- <div tal:condition="packagings/batch"
229- tal:content="cache:public, 30 minute">
230+ <tal:cache content="cache:public, 30 minute">
231+ <div tal:condition="packagings/batch">
232 <h2>All upstream links</h2>
233
234 <div class="results"
235@@ -82,6 +82,7 @@
236 <div class="results"
237 tal:content="structure packagings/@@+navigation-links-lower" />
238 </div>
239+ </tal:cache>
240 </div>
241 </body>
242 </html>
243
244=== added file 'lib/lp/testing/memcache.py'
245--- lib/lp/testing/memcache.py 1970-01-01 00:00:00 +0000
246+++ lib/lp/testing/memcache.py 2010-06-30 20:05:55 +0000
247@@ -0,0 +1,36 @@
248+# Copyright 2010 Canonical Ltd. This software is licensed under the
249+# GNU Affero General Public License version 3 (see the file LICENSE).
250+
251+__metaclass__ = type
252+
253+from canonical.testing import LaunchpadFunctionalLayer
254+
255+from lp.testing import TestCaseWithFactory
256+
257+
258+class MemcacheTestCase(TestCaseWithFactory):
259+
260+ layer = LaunchpadFunctionalLayer
261+
262+ def assertCacheMiss(self, fragment, content):
263+ # Verify that fragment is not cached in the content.
264+ self.assertTrue(fragment in content)
265+ before, after = content.split(fragment, 1)
266+ cache_start = '<!-- Cache hit: memcache expression'
267+ if cache_start in before:
268+ # Verify that the preceding cache is not for this fragment
269+ cache_end = '<!-- End cache hit'
270+ self.assertTrue(cache_end in before)
271+ ignore, start = before.rsplit(cache_end, 1)
272+ self.assertTrue(cache_start not in start)
273+
274+ def assertCacheHit(self, fragment, expression, content):
275+ # Verify that fragment is cached by the specific expression in
276+ # the content.
277+ self.assertTrue(fragment in content)
278+ before, after = content.split(fragment, 1)
279+ cache_start = (
280+ '<!-- Cache hit: memcache expression (%s) ' % expression)
281+ self.assertTrue(cache_start in before)
282+ ignore, start = before.rsplit(cache_start, 1)
283+ self.assertTrue('<!-- End cache hit' not in start)