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
=== modified file 'lib/lp/registry/browser/tests/milestone-views.txt'
--- lib/lp/registry/browser/tests/milestone-views.txt 2010-06-29 14:52:51 +0000
+++ lib/lp/registry/browser/tests/milestone-views.txt 2010-06-30 20:05:55 +0000
@@ -248,7 +248,7 @@
248the product release data for a milestone.248the product release data for a milestone.
249249
250 >>> from canonical.launchpad.testing.pages import (250 >>> from canonical.launchpad.testing.pages import (
251 ... extract_text, find_main_content, find_tag_by_id)251 ... extract_text, find_tag_by_id)
252252
253 >>> view = create_view(253 >>> view = create_view(
254 ... milestone, '+productrelease-data', principal=person)254 ... milestone, '+productrelease-data', principal=person)
@@ -301,55 +301,6 @@
301 >>> login_person(person)301 >>> login_person(person)
302302
303303
304Milestone caching
305-----------------
306
307Active milestones cache bugtasks for 10 minutes.
308
309 >>> from canonical.testing.layers import MemcachedLayer
310
311 >>> MemcachedLayer.purge()
312 >>> cached_milestone = factory.makeMilestone(
313 ... 'cache-test', productseries=series)
314 >>> cached_bugtask = factory.makeBugTask(target=cached_milestone.product)
315 >>> cached_bugtask.milestone = cached_milestone
316 >>> view = create_view(cached_milestone, '+index', principal=person)
317 >>> view.expire_cache_minutes
318 10
319
320 >>> 'Cache hit' in view.render()
321 False
322
323 >>> content = find_main_content(create_view(
324 ... cached_milestone, '+index', principal=person).render())
325 >>> print find_tag_by_id(content, 'milestone-activities')
326 <div id="milestone-activities" ...<!--
327 Cache hit: memcache expression
328 (private, view/expire_cache_minutes minute) [600 seconds] ...
329 <dt>Assigned to you:</dt> ... No blueprints or bugs assigned to you. ...
330
331 >>> print find_tag_by_id(content, 'milestone-specs-bugs')
332 <div id="milestone-specs-bugs" ...
333 <!-- Cache hit: memcache expression
334 (private, view/expire_cache_minutes minute) [600 seconds] -->
335 <table class="listing sortable" id="milestone_bugtasks">...
336
337Inactive milestones cache bugtasks for 3 hours.
338
339 >>> MemcachedLayer.purge()
340 >>> cached_milestone.active = False
341 >>> view = create_view(cached_milestone, '+index', principal=person)
342 >>> view.expire_cache_minutes
343 360
344 >>> 'Cache hit' in view.render()
345 False
346 >>> print create_view(
347 ... cached_milestone, '+index', principal=person).render()
348 <...<!-- Cache hit: memcache expression
349 (private, view/expire_cache_minutes minute) [21600 seconds] -->
350 <table class="listing sortable" id="milestone_bugtasks">...
351
352
353ProjectGroup milestones304ProjectGroup milestones
354-----------------------305-----------------------
355306
356307
=== added file 'lib/lp/registry/browser/tests/test_milestone.py'
--- lib/lp/registry/browser/tests/test_milestone.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/browser/tests/test_milestone.py 2010-06-30 20:05:55 +0000
@@ -0,0 +1,64 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Test milestone views."""
5
6__metaclass__ = type
7
8import unittest
9
10from lp.testing import login_person
11from lp.testing.views import create_initialized_view
12from lp.testing.memcache import MemcacheTestCase
13
14
15class TestMilestoneMemcache(MemcacheTestCase):
16
17 def setUp(self):
18 super(TestMilestoneMemcache, self).setUp()
19 product = self.factory.makeProduct()
20 login_person(product.owner)
21 series = self.factory.makeProductSeries(product=product)
22 self.milestone = self.factory.makeMilestone(
23 productseries=series, name="1.1")
24 bugtask = self.factory.makeBugTask(target=product)
25 bugtask.transitionToAssignee(product.owner)
26 bugtask.milestone = self.milestone
27 self.observer = self.factory.makePerson()
28
29 def test_milestone_index_memcache(self):
30 # Miss the cache on first render.
31 view = create_initialized_view(
32 self.milestone, name='+index', principal=self.observer)
33 content = view.render()
34 self.assertCacheMiss('<dt>Assigned to you:</dt>', content)
35 self.assertCacheMiss('id="milestone_bugtasks"', content)
36 # Hit the cache on the second render.
37 view = create_initialized_view(
38 self.milestone, name='+index', principal=self.observer)
39 self.assertTrue(view.milestone.active)
40 self.assertEqual(10, view.expire_cache_minutes)
41 content = view.render()
42 self.assertCacheHit(
43 '<dt>Assigned to you:</dt>',
44 'private, view/expire_cache_minutes minute', content)
45 self.assertCacheHit(
46 'id="milestone_bugtasks"',
47 'private, view/expire_cache_minutes minute', content)
48
49 def test_milestone_index_active_cache_time(self):
50 # Verify the active milestone cache time.
51 view = create_initialized_view(self.milestone, name='+index')
52 self.assertTrue(view.milestone.active)
53 self.assertEqual(10, view.expire_cache_minutes)
54
55 def test_milestone_index_inactive_cache_time(self):
56 # Verify the inactive milestone cache time.
57 self.milestone.active = False
58 view = create_initialized_view(self.milestone, name='+index')
59 self.assertFalse(view.milestone.active)
60 self.assertEqual(360, view.expire_cache_minutes)
61
62
63def test_suite():
64 return unittest.TestLoader().loadTestsFromName(__name__)
065
=== modified file 'lib/lp/registry/browser/tests/test_packaging.py'
--- lib/lp/registry/browser/tests/test_packaging.py 2010-04-16 19:28:24 +0000
+++ lib/lp/registry/browser/tests/test_packaging.py 2010-06-30 20:05:55 +0000
@@ -15,6 +15,7 @@
15from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet15from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
16from lp.testing import TestCaseWithFactory16from lp.testing import TestCaseWithFactory
17from lp.testing.views import create_initialized_view17from lp.testing.views import create_initialized_view
18from lp.testing.memcache import MemcacheTestCase
18from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities19from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
19from canonical.launchpad.ftests import login, logout20from canonical.launchpad.ftests import login, logout
20from canonical.launchpad.testing.pages import setupBrowser21from canonical.launchpad.testing.pages import setupBrowser
@@ -165,5 +166,39 @@
165 distroseries=distroseries))166 distroseries=distroseries))
166167
167168
169class TestDistroseriesPackagingMemcache(MemcacheTestCase):
170 """Tests distroseries packaging cache rules."""
171
172 def setUp(self):
173 super(TestDistroseriesPackagingMemcache, self).setUp()
174 ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
175 self.hoary = ubuntu.getSeries('hoary')
176 self.observer = self.factory.makePerson()
177
178 def test_needs_packaging_memcache(self):
179 # Verify that the packages table is cached.
180 # Miss the cache on first render.
181 view = create_initialized_view(
182 self.hoary, name='+needs-packaging', principal=self.observer)
183 self.assertCacheMiss('<table id="packages"', view.render())
184 # Hit the cache on the second render.
185 view = create_initialized_view(
186 self.hoary, name='+needs-packaging', principal=self.observer)
187 self.assertCacheHit(
188 '<table id="packages"', 'public, 30 minute', view.render())
189
190 def test_packaging_memcache(self):
191 # Verify that the packagings table is cached.
192 # Miss the cache on first render.
193 view = create_initialized_view(
194 self.hoary, name='+packaging', principal=self.observer)
195 self.assertCacheMiss('<table id="packagings"', view.render())
196 # Hit the cache on the second render.
197 view = create_initialized_view(
198 self.hoary, name='+packaging', principal=self.observer)
199 self.assertCacheHit(
200 '<table id="packagings"', 'public, 30 minute', view.render())
201
202
168def test_suite():203def test_suite():
169 return TestLoader().loadTestsFromName(__name__)204 return TestLoader().loadTestsFromName(__name__)
170205
=== modified file 'lib/lp/registry/templates/distroseries-needs-packaging.pt'
--- lib/lp/registry/templates/distroseries-needs-packaging.pt 2010-06-29 15:03:03 +0000
+++ lib/lp/registry/templates/distroseries-needs-packaging.pt 2010-06-30 20:05:55 +0000
@@ -31,6 +31,7 @@
31 </ul>31 </ul>
32 </div>32 </div>
3333
34 <tal:cache content="cache:public, 30 minute">
34 <div class="narrow"35 <div class="narrow"
35 tal:define="summaries view/cached_unlinked_packages;"36 tal:define="summaries view/cached_unlinked_packages;"
36 tal:condition="summaries/batch">37 tal:condition="summaries/batch">
@@ -47,8 +48,7 @@
47 </thead>48 </thead>
4849
49 <tbody>50 <tbody>
50 <tr tal:repeat="summary summaries/batch"51 <tr tal:repeat="summary summaries/batch">
51 tal:content="cache:public, 30 minute">
52 <td>52 <td>
53 <a class="sprite package-source"53 <a class="sprite package-source"
54 tal:attributes="href summary/package/fmt:url"54 tal:attributes="href summary/package/fmt:url"
@@ -87,6 +87,7 @@
87 <div class="results"87 <div class="results"
88 tal:content="structure summaries/@@+navigation-links-lower" />88 tal:content="structure summaries/@@+navigation-links-lower" />
89 </div>89 </div>
90 </tal:cache>
90 </div>91 </div>
91 </body>92 </body>
92</html>93</html>
9394
=== modified file 'lib/lp/registry/templates/distroseries-packaging.pt'
--- lib/lp/registry/templates/distroseries-packaging.pt 2010-06-29 15:03:03 +0000
+++ lib/lp/registry/templates/distroseries-packaging.pt 2010-06-30 20:05:55 +0000
@@ -31,8 +31,8 @@
31 </ul>31 </ul>
32 </div>32 </div>
3333
34 <div tal:condition="packagings/batch"34 <tal:cache content="cache:public, 30 minute">
35 tal:content="cache:public, 30 minute">35 <div tal:condition="packagings/batch">
36 <h2>All upstream links</h2>36 <h2>All upstream links</h2>
3737
38 <div class="results"38 <div class="results"
@@ -82,6 +82,7 @@
82 <div class="results"82 <div class="results"
83 tal:content="structure packagings/@@+navigation-links-lower" />83 tal:content="structure packagings/@@+navigation-links-lower" />
84 </div>84 </div>
85 </tal:cache>
85 </div>86 </div>
86 </body>87 </body>
87</html>88</html>
8889
=== added file 'lib/lp/testing/memcache.py'
--- lib/lp/testing/memcache.py 1970-01-01 00:00:00 +0000
+++ lib/lp/testing/memcache.py 2010-06-30 20:05:55 +0000
@@ -0,0 +1,36 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4__metaclass__ = type
5
6from canonical.testing import LaunchpadFunctionalLayer
7
8from lp.testing import TestCaseWithFactory
9
10
11class MemcacheTestCase(TestCaseWithFactory):
12
13 layer = LaunchpadFunctionalLayer
14
15 def assertCacheMiss(self, fragment, content):
16 # Verify that fragment is not cached in the content.
17 self.assertTrue(fragment in content)
18 before, after = content.split(fragment, 1)
19 cache_start = '<!-- Cache hit: memcache expression'
20 if cache_start in before:
21 # Verify that the preceding cache is not for this fragment
22 cache_end = '<!-- End cache hit'
23 self.assertTrue(cache_end in before)
24 ignore, start = before.rsplit(cache_end, 1)
25 self.assertTrue(cache_start not in start)
26
27 def assertCacheHit(self, fragment, expression, content):
28 # Verify that fragment is cached by the specific expression in
29 # the content.
30 self.assertTrue(fragment in content)
31 before, after = content.split(fragment, 1)
32 cache_start = (
33 '<!-- Cache hit: memcache expression (%s) ' % expression)
34 self.assertTrue(cache_start in before)
35 ignore, start = before.rsplit(cache_start, 1)
36 self.assertTrue('<!-- End cache hit' not in start)