Merge lp:~michael.nelson/launchpad/429318-copy-archives-timeout into lp:launchpad

Proposed by Michael Nelson
Status: Merged
Approved by: Julian Edwards
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~michael.nelson/launchpad/429318-copy-archives-timeout
Merge into: lp:launchpad
Diff against target: None lines
To merge this branch: bzr merge lp:~michael.nelson/launchpad/429318-copy-archives-timeout
Reviewer Review Type Date Requested Status
Julian Edwards (community) Approve
Review via email: mp+11896@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Michael Nelson (michael.nelson) wrote :

= Summary =

This branch aims to fix bug 429318 by replacing the code used for
ArchiveView.num_pkgs_building with storm-code, so that (1) the unused
prefetch data is not included in the result, and (2) the builds do not
need to be iterated.

== Proposed fix ==

See the description on bug 429318.

== Pre-implementation notes ==

I'm not sure if selecting the SourcePackageReleases is the right thing -
 I'm only using them to count etc. I could also use
SourcePackagePublishingHistory - but (1) it would be an extra join, and
(2) there are already methods on IArchive for this (that do not enable
me to do what I need here.)

== Implementation details ==

I've had to use removeSecurityProxy to be able to get to the difference
method of ResultSet - this is because the interface at
zope.storm.interfaces.IResultSet does not include difference, or
_find_spec (which is accessed by the difference method).

I'll create a bug for this and XXX it.

== Tests ==

bin/test -vv -t archive-views.txt -t TestGetSourcePackageReleases

== Demo and Q/A ==

No UI change.

To Q/A:
https://edge.launchpad.net/ubuntu/+archive/test-rebuild-20090909/+index

(currently times out).

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/soyuz/tests/test_archive.py
  lib/lp/soyuz/interfaces/archive.py
  lib/lp/soyuz/model/archive.py
  lib/lp/soyuz/browser/archive.py

== Pylint notices ==

lib/lp/soyuz/interfaces/archive.py
    38: [F0401] Unable to import 'lazr.enum' (No module named enum)
    51: [F0401] Unable to import 'lazr.restful.declarations' (No module
named restful)
    57: [F0401] Unable to import 'lazr.restful.fields' (No module named
restful)

lib/lp/soyuz/model/archive.py
    14: [F0401] Unable to import 'lazr.lifecycle.event' (No module named
lifecycle)

--
Michael

Revision history for this message
Julian Edwards (julian-edwards) wrote :
Download full text (5.1 KiB)

 review needs-fixing

Thanks for this fix Michael. I have the usual comments in-line.

On Wednesday 16 September 2009 16:55:13 Michael Nelson wrote:
> I'm not sure if selecting the SourcePackageReleases is the right thing -
> I'm only using them to count etc. I could also use
> SourcePackagePublishingHistory - but (1) it would be an extra join, and
> (2) there are already methods on IArchive for this (that do not enable
> me to do what I need here.)

I think it's fine - in fact the old way of doing it was pretty inefficient
even without the prejoin problem.

> I've had to use removeSecurityProxy to be able to get to the difference
> method of ResultSet - this is because the interface at
> zope.storm.interfaces.IResultSet does not include difference, or
> _find_spec (which is accessed by the difference method).
>
> I'll create a bug for this and XXX it.

Ok, thanks.

>+ def getSourcePackageReleases(buildstatus=None):

Can you make this build_status please.

>+ """Return the releases for this archive.
>+
>+ :param buildstatus: If specified, only the distinct releases with
>+ builds in the specified build status will be returned.
>+ :return: A `ResultSet` of distinct `SourcePackageReleases` for this
>+ archive.
>+ """
>

>+ def getSourcePackageReleases(self, buildstatus=None):

And here of course.

>+ """See `IArchive`."""
>+ store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)

Is using the main store deliberate? I would have thought
 store = Store.of(self)
should suffice?

>+
>+ extra_exprs = []
>+ if buildstatus is not None:
>+ extra_exprs.append(Build.buildstate == buildstatus)
>+
>+ result_set = store.find(SourcePackageRelease,

PEP-8 - line feed after the open paren. please.

>+ Build.sourcepackagereleaseID == SourcePackageRelease.id,
>+ Build.archive == self,
>+ *extra_exprs)
>+
>+ result_set.config(distinct=True).order_by(SourcePackageRelease.id)
>+ return result_set
>
> class ArchiveSet:
> implements(IArchiveSet)
>
>=== modified file 'lib/lp/soyuz/tests/test_archive.py'
>--- lib/lp/soyuz/tests/test_archive.py 2009-07-27 14:45:01 +0000
>+++ lib/lp/soyuz/tests/test_archive.py 2009-09-16 15:22:15 +0000
>@@ -14,6 +14,7 @@
> from lp.registry.interfaces.distribution import IDistributionSet
> from lp.soyuz.interfaces.archive import IArchiveSet
> from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFormat
>+from lp.soyuz.interfaces.build import BuildStatus
> from lp.soyuz.interfaces.publishing import PackagePublishingStatus
> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
> from lp.testing import TestCaseWithFactory
>@@ -243,5 +244,54 @@
> [u'1.0', u'0.5'], versions,
> "The latest version was not first.")
>
>+
>+class TestGetSourcePackageReleases(TestCaseWithFactory):
>+
>+ layer = LaunchpadZopelessLayer
>+
>+ def setUp(self):
>+ super(TestGetSourcePackageReleases, self).setUp()
>+ self.publisher = SoyuzTestPublisher()
>+ self.publisher.prepareBreezyAutotest()
>+
>+ # Create an archive wit...

Read more...

review: Needs Fixing
Revision history for this message
Michael Nelson (michael.nelson) wrote :
Download full text (6.2 KiB)

Julian Edwards wrote:
> Review: Needs Fixing
> review needs-fixing
>
> Thanks for this fix Michael. I have the usual comments in-line.
>
> On Wednesday 16 September 2009 16:55:13 Michael Nelson wrote:
>> I'm not sure if selecting the SourcePackageReleases is the right thing -
>> I'm only using them to count etc. I could also use
>> SourcePackagePublishingHistory - but (1) it would be an extra join, and
>> (2) there are already methods on IArchive for this (that do not enable
>> me to do what I need here.)
>
> I think it's fine - in fact the old way of doing it was pretty inefficient
> even without the prejoin problem.

Yes - I was only thinking of PPAs when I wrote it. So I'm all for the
new version, I just wasn't sure if selecting SourcePackageReleases was
the best way.

>
>> I've had to use removeSecurityProxy to be able to get to the difference
>> method of ResultSet - this is because the interface at
>> zope.storm.interfaces.IResultSet does not include difference, or
>> _find_spec (which is accessed by the difference method).
>>
>> I'll create a bug for this and XXX it.
>
> Ok, thanks.
>

Added XXX with bug 431203.

>> + def getSourcePackageReleases(buildstatus=None):
>
> Can you make this build_status please.
>

Done.

>> + """Return the releases for this archive.
>> +
>> + :param buildstatus: If specified, only the distinct releases with
>> + builds in the specified build status will be returned.
>> + :return: A `ResultSet` of distinct `SourcePackageReleases` for this
>> + archive.
>> + """
>>
>
>> + def getSourcePackageReleases(self, buildstatus=None):
>
> And here of course.

Yep.

>
>> + """See `IArchive`."""
>> + store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
>
> Is using the main store deliberate? I would have thought
> store = Store.of(self)
> should suffice?

Done.

>
>> +
>> + extra_exprs = []
>> + if buildstatus is not None:
>> + extra_exprs.append(Build.buildstate == buildstatus)
>> +
>> + result_set = store.find(SourcePackageRelease,
>
> PEP-8 - line feed after the open paren. please.

Gar - sorry.

>
>> + Build.sourcepackagereleaseID == SourcePackageRelease.id,
>> + Build.archive == self,
>> + *extra_exprs)
>> +
>> + result_set.config(distinct=True).order_by(SourcePackageRelease.id)
>> + return result_set
>>
>> class ArchiveSet:
>> implements(IArchiveSet)
>>
>> === modified file 'lib/lp/soyuz/tests/test_archive.py'
>> --- lib/lp/soyuz/tests/test_archive.py 2009-07-27 14:45:01 +0000
>> +++ lib/lp/soyuz/tests/test_archive.py 2009-09-16 15:22:15 +0000
>> @@ -14,6 +14,7 @@
>> from lp.registry.interfaces.distribution import IDistributionSet
>> from lp.soyuz.interfaces.archive import IArchiveSet
>> from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFormat
>> +from lp.soyuz.interfaces.build import BuildStatus
>> from lp.soyuz.interfaces.publishing import PackagePublishingStatus
>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
>> from lp.testing import TestCaseWithFactory
>> @@ -243,5 +244,54 @@
>> ...

Read more...

1=== modified file 'lib/lp/soyuz/browser/archive.py'
2--- lib/lp/soyuz/browser/archive.py 2009-09-16 15:38:07 +0000
3+++ lib/lp/soyuz/browser/archive.py 2009-09-17 07:13:40 +0000
4@@ -852,18 +852,18 @@
5 """Return the number of building/waiting to build packages."""
6
7 sprs_building = self.context.getSourcePackageReleases(
8- buildstatus = BuildStatus.BUILDING)
9+ build_status = BuildStatus.BUILDING)
10 sprs_waiting = self.context.getSourcePackageReleases(
11- buildstatus = BuildStatus.NEEDSBUILD)
12+ build_status = BuildStatus.NEEDSBUILD)
13
14 pkgs_building_count = sprs_building.count()
15
16 # A package is not counted as waiting if it already has at least
17 # one build building.
18- # Note: because neither the 'difference' method or the '_find_spec'
19- # property are exposed via storm.zope.interfaces.IResultSet,
20- # we need to remove the proxy for both results to use
21- # the difference method.
22+ # XXX Michael Nelson 20090917 bug 431203. Because neither the
23+ # 'difference' method or the '_find_spec' property are exposed via
24+ # storm.zope.interfaces.IResultSet, we need to remove the proxy for
25+ # both results to use the difference method.
26 naked_sprs_waiting = removeSecurityProxy(sprs_waiting)
27 naked_sprs_building = removeSecurityProxy(sprs_building)
28
29
30=== modified file 'lib/lp/soyuz/doc/archive.txt'
31--- lib/lp/soyuz/doc/archive.txt 2009-08-28 06:41:25 +0000
32+++ lib/lp/soyuz/doc/archive.txt 2009-09-17 07:12:09 +0000
33@@ -888,6 +888,25 @@
34 2
35
36
37+== Getting an Archive's source-package releases ==
38+
39+The method getSourcePackageReleases() is provided to return the unique
40+source-package releases for the archive. By default, all releases will
41+be returned, but you can also ask for releases with builds in a certain
42+state.
43+
44+ >>> from lp.soyuz.interfaces.build import BuildStatus
45+ >>> releases = cprov_archive.getSourcePackageReleases(
46+ ... build_status=BuildStatus.FULLYBUILT)
47+ >>> for release in releases:
48+ ... print release.title
49+ mozilla-firefox - 0.9
50+ pmount - 0.1-1
51+ iceweasel - 1.0
52+
53+For further details see the `TestGetSourcePackageReleases` unit-test.
54+
55+
56 == Sources available for deletions ==
57
58 'getSourcesForDeletion' is the base for '+delete-packages' page on PPA
59
60=== modified file 'lib/lp/soyuz/interfaces/archive.py'
61--- lib/lp/soyuz/interfaces/archive.py 2009-09-16 15:22:15 +0000
62+++ lib/lp/soyuz/interfaces/archive.py 2009-09-17 07:12:09 +0000
63@@ -739,10 +739,10 @@
64 :return: True if the person is allowed to upload the source package.
65 """
66
67- def getSourcePackageReleases(buildstatus=None):
68+ def getSourcePackageReleases(build_status=None):
69 """Return the releases for this archive.
70
71- :param buildstatus: If specified, only the distinct releases with
72+ :param build_status: If specified, only the distinct releases with
73 builds in the specified build status will be returned.
74 :return: A `ResultSet` of distinct `SourcePackageReleases` for this
75 archive.
76
77=== modified file 'lib/lp/soyuz/model/archive.py'
78--- lib/lp/soyuz/model/archive.py 2009-09-16 15:22:15 +0000
79+++ lib/lp/soyuz/model/archive.py 2009-09-17 07:12:09 +0000
80@@ -1186,15 +1186,16 @@
81
82 return subscription
83
84- def getSourcePackageReleases(self, buildstatus=None):
85+ def getSourcePackageReleases(self, build_status=None):
86 """See `IArchive`."""
87- store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
88+ store = Store.of(self)
89
90 extra_exprs = []
91- if buildstatus is not None:
92- extra_exprs.append(Build.buildstate == buildstatus)
93+ if build_status is not None:
94+ extra_exprs.append(Build.buildstate == build_status)
95
96- result_set = store.find(SourcePackageRelease,
97+ result_set = store.find(
98+ SourcePackageRelease,
99 Build.sourcepackagereleaseID == SourcePackageRelease.id,
100 Build.archive == self,
101 *extra_exprs)
102
103=== modified file 'lib/lp/soyuz/tests/test_archive.py'
104--- lib/lp/soyuz/tests/test_archive.py 2009-09-16 15:22:15 +0000
105+++ lib/lp/soyuz/tests/test_archive.py 2009-09-17 07:12:09 +0000
106@@ -287,7 +287,7 @@
107 build.buildstate = BuildStatus.NEEDSBUILD
108
109 result = self.archive.getSourcePackageReleases(
110- buildstatus=BuildStatus.NEEDSBUILD)
111+ build_status=BuildStatus.NEEDSBUILD)
112
113 self.failUnlessEqual(1, result.count())
114 self.failUnlessEqual(
Revision history for this message
Julian Edwards (julian-edwards) wrote :

 merge approve
 review approve

On Thursday 17 September 2009 08:18:08 Michael Nelson wrote:
> Yes - I was only thinking of PPAs when I wrote it. So I'm all for the
> new version, I just wasn't sure if selecting SourcePackageReleases was
> the best way.

Well I was thinking of the previous use of Python sets rather than distinct
SQL clauses :)

> Yes, I wonder whether it would even be worthwhile splitting the content
> class up using mixins to match the documentation. Also think it would be
> great to improve the documentation by moving lots of the unit tests
> there to actual python unittests, leaving the documentation much more
> readable. But I think that would take planning and time?

Yes, it's certainly something for a rainy day.

For me, doctests should contain basic instructions on how to use the content
class and its methods. Testing corner cases belong in a unit test, but I
don't think your new test is that.

Our problem is that Soyuz depends heavily on setting up a lot of data, so
doctests tend to be full of calls to the STP and factory, which muddies the
story, so I can see why you made it a unit test.

Ho hum.

> For the moment, I've added a brief section in archive.txt with a
> reference to the unit test. See what you think.

It's fine, thanks.

Land it!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/soyuz/browser/archive.py'
2--- lib/lp/soyuz/browser/archive.py 2009-09-11 17:55:23 +0000
3+++ lib/lp/soyuz/browser/archive.py 2009-09-16 15:38:07 +0000
4@@ -33,6 +33,7 @@
5 from zope.component import getUtility
6 from zope.formlib import form
7 from zope.interface import implements, Interface
8+from zope.security.proxy import removeSecurityProxy
9 from zope.schema import Choice, List, TextLine
10 from zope.schema.interfaces import IContextSourceBinder
11 from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
12@@ -850,23 +851,24 @@
13 def num_pkgs_building(self):
14 """Return the number of building/waiting to build packages."""
15
16- building = getUtility(IBuildSet).getBuildsForArchive(
17- self.context, status=BuildStatus.BUILDING)
18- needs_build = getUtility(IBuildSet).getBuildsForArchive(
19- self.context, status=BuildStatus.NEEDSBUILD)
20-
21- # Create a set of all source package releases that have builds
22- # waiting to build, as well as a set of those with building builds.
23- needs_build_set = set(
24- build.sourcepackagerelease for build in needs_build)
25-
26- building_set = set(
27- build.sourcepackagerelease for build in building)
28+ sprs_building = self.context.getSourcePackageReleases(
29+ buildstatus = BuildStatus.BUILDING)
30+ sprs_waiting = self.context.getSourcePackageReleases(
31+ buildstatus = BuildStatus.NEEDSBUILD)
32+
33+ pkgs_building_count = sprs_building.count()
34
35 # A package is not counted as waiting if it already has at least
36 # one build building.
37- pkgs_building_count = len(building_set)
38- pkgs_waiting_count = len(needs_build_set.difference(building_set))
39+ # Note: because neither the 'difference' method or the '_find_spec'
40+ # property are exposed via storm.zope.interfaces.IResultSet,
41+ # we need to remove the proxy for both results to use
42+ # the difference method.
43+ naked_sprs_waiting = removeSecurityProxy(sprs_waiting)
44+ naked_sprs_building = removeSecurityProxy(sprs_building)
45+
46+ pkgs_waiting_count = naked_sprs_waiting.difference(
47+ naked_sprs_building).count()
48
49 # The total is just used for conditionals in the template.
50 return {
51
52=== modified file 'lib/lp/soyuz/interfaces/archive.py'
53--- lib/lp/soyuz/interfaces/archive.py 2009-09-02 21:28:11 +0000
54+++ lib/lp/soyuz/interfaces/archive.py 2009-09-16 15:22:15 +0000
55@@ -739,6 +739,14 @@
56 :return: True if the person is allowed to upload the source package.
57 """
58
59+ def getSourcePackageReleases(buildstatus=None):
60+ """Return the releases for this archive.
61+
62+ :param buildstatus: If specified, only the distinct releases with
63+ builds in the specified build status will be returned.
64+ :return: A `ResultSet` of distinct `SourcePackageReleases` for this
65+ archive.
66+ """
67
68 class IArchiveView(IHasBuildRecords):
69 """Archive interface for operations restricted by view privilege."""
70
71=== modified file 'lib/lp/soyuz/model/archive.py'
72--- lib/lp/soyuz/model/archive.py 2009-09-10 12:59:56 +0000
73+++ lib/lp/soyuz/model/archive.py 2009-09-16 15:22:15 +0000
74@@ -1186,6 +1186,21 @@
75
76 return subscription
77
78+ def getSourcePackageReleases(self, buildstatus=None):
79+ """See `IArchive`."""
80+ store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
81+
82+ extra_exprs = []
83+ if buildstatus is not None:
84+ extra_exprs.append(Build.buildstate == buildstatus)
85+
86+ result_set = store.find(SourcePackageRelease,
87+ Build.sourcepackagereleaseID == SourcePackageRelease.id,
88+ Build.archive == self,
89+ *extra_exprs)
90+
91+ result_set.config(distinct=True).order_by(SourcePackageRelease.id)
92+ return result_set
93
94 class ArchiveSet:
95 implements(IArchiveSet)
96
97=== modified file 'lib/lp/soyuz/tests/test_archive.py'
98--- lib/lp/soyuz/tests/test_archive.py 2009-07-27 14:45:01 +0000
99+++ lib/lp/soyuz/tests/test_archive.py 2009-09-16 15:22:15 +0000
100@@ -14,6 +14,7 @@
101 from lp.registry.interfaces.distribution import IDistributionSet
102 from lp.soyuz.interfaces.archive import IArchiveSet
103 from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFormat
104+from lp.soyuz.interfaces.build import BuildStatus
105 from lp.soyuz.interfaces.publishing import PackagePublishingStatus
106 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
107 from lp.testing import TestCaseWithFactory
108@@ -243,5 +244,54 @@
109 [u'1.0', u'0.5'], versions,
110 "The latest version was not first.")
111
112+
113+class TestGetSourcePackageReleases(TestCaseWithFactory):
114+
115+ layer = LaunchpadZopelessLayer
116+
117+ def setUp(self):
118+ super(TestGetSourcePackageReleases, self).setUp()
119+ self.publisher = SoyuzTestPublisher()
120+ self.publisher.prepareBreezyAutotest()
121+
122+ # Create an archive with some published binaries.
123+ self.archive = self.factory.makeArchive()
124+ binaries_foo = self.publisher.getPubBinaries(
125+ archive=self.archive, binaryname="foo-bin")
126+ binaries_bar = self.publisher.getPubBinaries(
127+ archive=self.archive, binaryname="bar-bin")
128+
129+ # Collect the builds for reference.
130+ self.builds_foo = [
131+ binary.binarypackagerelease.build for binary in binaries_foo]
132+ self.builds_bar = [
133+ binary.binarypackagerelease.build for binary in binaries_bar]
134+
135+ # Collect the source package releases for reference.
136+ self.sourcepackagereleases = [
137+ self.builds_foo[0].sourcepackagerelease,
138+ self.builds_bar[0].sourcepackagerelease,
139+ ]
140+
141+ def test_getSourcePackageReleases_with_no_params(self):
142+ # With no params all source package releases are returned.
143+ sprs = self.archive.getSourcePackageReleases()
144+
145+ self.assertContentEqual(self.sourcepackagereleases, sprs)
146+
147+ def test_getSourcePackageReleases_with_buildstatus(self):
148+ # Results are filtered by the specified buildstatus.
149+
150+ # Set the builds for one of the sprs to needs build.
151+ for build in self.builds_foo:
152+ build.buildstate = BuildStatus.NEEDSBUILD
153+
154+ result = self.archive.getSourcePackageReleases(
155+ buildstatus=BuildStatus.NEEDSBUILD)
156+
157+ self.failUnlessEqual(1, result.count())
158+ self.failUnlessEqual(
159+ self.sourcepackagereleases[0], result[0])
160+
161 def test_suite():
162 return unittest.TestLoader().loadTestsFromName(__name__)