~launchpad-pqm/launchpad/devel

14538.2.49 by Curtis Hovey
Updated copyright.
1
# Copyright 2010-2011 Canonical Ltd.  This software is licensed under the
11887.1.2 by Ian Booth
Initial coding
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
4
# pylint: disable-msg=E0611,W0212
5
6
__metaclass__ = type
7
8
__all__ = [
9
    'RecipeBuildRecord',
10
    'RecipeBuildRecordSet',
11
    ]
12
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
13
from collections import namedtuple
11887.3.10 by Ian Booth
Style guide fixes
14
from datetime import (
15
    datetime,
16
    timedelta,
17
    )
11887.1.2 by Ian Booth
Initial coding
18
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
19
import pytz
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
20
from storm import Undef
11887.1.2 by Ian Booth
Initial coding
21
from storm.expr import (
12425.1.2 by Ian Booth
Quick stab at improving daily recipe builds query
22
    Desc,
11887.1.2 by Ian Booth
Initial coding
23
    Join,
24
    Max,
12425.1.2 by Ian Booth
Quick stab at improving daily recipe builds query
25
    Select,
26
    )
11887.1.2 by Ian Booth
Initial coding
27
from zope.interface import implements
28
29
from lp.buildmaster.enums import BuildStatus
30
from lp.buildmaster.model.buildfarmjob import BuildFarmJob
31
from lp.buildmaster.model.packagebuild import PackageBuild
11887.1.5 by Ian Booth
Next round
32
from lp.code.interfaces.recipebuild import IRecipeBuildRecordSet
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
33
from lp.code.model.sourcepackagerecipe import SourcePackageRecipe
11887.1.2 by Ian Booth
Initial coding
34
from lp.code.model.sourcepackagerecipebuild import SourcePackageRecipeBuild
35
from lp.registry.model.person import Person
36
from lp.registry.model.sourcepackagename import SourcePackageName
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
37
from lp.services.database.decoratedresultset import DecoratedResultSet
14575.1.1 by Jeroen Vermeulen
Lint.
38
from lp.services.database.lpstorm import ISlaveStore
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
39
from lp.services.database.stormexpr import CountDistinct
14612.2.1 by William Grant
format-imports on lib/. So many imports.
40
from lp.services.webapp.publisher import canonical_url
11887.1.2 by Ian Booth
Initial coding
41
from lp.soyuz.model.archive import Archive
42
from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
43
from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
44
45
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
46
class RecipeBuildRecord(namedtuple(
47
    'RecipeBuildRecord',
12060.5.1 by Ian Booth
Initial coding
48
    """sourcepackagename, recipeowner, archive, recipe,
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
49
        most_recent_build_time""")):
12510.3.5 by Ian Booth
Add check to ensure recipe is not none when creating RecipeBuildRecord
50
12510.3.6 by Ian Booth
Make __new__ args more explicit
51
    def __new__(cls, sourcepackagename, recipeowner, archive, recipe,
52
                most_recent_build_time):
12510.3.5 by Ian Booth
Add check to ensure recipe is not none when creating RecipeBuildRecord
53
        # Ensure that a valid (not None) recipe is used. This may change in
54
        # future if we support build records with no recipe.
55
        assert recipe is not None, "RecipeBuildRecord requires a recipe."
12510.3.6 by Ian Booth
Make __new__ args more explicit
56
        self = super(RecipeBuildRecord, cls).__new__(
57
            cls, sourcepackagename, recipeowner, archive, recipe,
58
            most_recent_build_time)
12510.3.5 by Ian Booth
Add check to ensure recipe is not none when creating RecipeBuildRecord
59
        return self
60
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
61
    # We need to implement our own equality check since __eq__ is broken on
62
    # SourcePackageRecipe. It's broken there because __eq__ is broken,
63
    # or not supported, on storm's ReferenceSet implementation.
64
    def __eq__(self, other):
65
        return (self.sourcepackagename == other.sourcepackagename
66
            and self.recipeowner == other.recipeowner
12060.5.1 by Ian Booth
Initial coding
67
            and self.recipe.name == other.recipe.name
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
68
            and self.archive == other.archive
11887.1.10 by Ian Booth
Lint fixes
69
            and self.most_recent_build_time == other.most_recent_build_time)
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
70
71
    def __hash__(self):
72
        return (
73
            hash(self.sourcepackagename.name) ^
74
            hash(self.recipeowner.name) ^
12060.5.1 by Ian Booth
Initial coding
75
            hash(self.recipe.name) ^
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
76
            hash(self.archive.name) ^
11887.1.10 by Ian Booth
Lint fixes
77
            hash(self.most_recent_build_time))
11887.1.2 by Ian Booth
Initial coding
78
11887.1.18 by Ian Booth
Fixes as per code review
79
    @property
80
    def distro_source_package(self):
12060.5.1 by Ian Booth
Initial coding
81
        return self.archive.distribution.getSourcePackage(
11887.1.18 by Ian Booth
Fixes as per code review
82
            self.sourcepackagename)
83
12510.3.1 by Ian Booth
More performance enhancements
84
    @property
85
    def recipe_name(self):
86
        return self.recipe.name
87
88
    @property
89
    def recipe_url(self):
90
        return canonical_url(self.recipe, rootsite='code')
91
11887.1.2 by Ian Booth
Initial coding
92
93
class RecipeBuildRecordSet:
94
    """See `IRecipeBuildRecordSet`."""
95
96
    implements(IRecipeBuildRecordSet)
97
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
98
    def findCompletedDailyBuilds(self, epoch_days=30):
11887.1.2 by Ian Booth
Initial coding
99
        """See `IRecipeBuildRecordSet`."""
100
11887.1.18 by Ian Booth
Fixes as per code review
101
        store = ISlaveStore(SourcePackageRecipe)
11887.1.2 by Ian Booth
Initial coding
102
        tables = [
103
            SourcePackageRecipe,
104
            Join(SourcePackageRecipeBuild,
105
                 SourcePackageRecipeBuild.recipe_id ==
106
                 SourcePackageRecipe.id),
107
            Join(SourcePackageRelease,
108
                 SourcePackageRecipeBuild.id ==
109
                 SourcePackageRelease.source_package_recipe_build_id),
12425.2.1 by Ian Booth
Rework record sort order and add some tests
110
            Join(SourcePackageName,
111
                 SourcePackageRelease.sourcepackagename ==
112
                 SourcePackageName.id),
11887.1.2 by Ian Booth
Initial coding
113
            Join(BinaryPackageBuild,
114
                 BinaryPackageBuild.source_package_release_id ==
115
                    SourcePackageRelease.id),
116
            Join(PackageBuild,
117
                 PackageBuild.id ==
118
                 BinaryPackageBuild.package_build_id),
119
            Join(BuildFarmJob,
120
                 BuildFarmJob.id ==
121
                 PackageBuild.build_farm_job_id),
122
        ]
123
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
124
        where = [BuildFarmJob.status == BuildStatus.FULLYBUILT,
125
                    SourcePackageRecipe.build_daily]
126
        if epoch_days is not None:
127
            epoch = datetime.now(pytz.UTC) - timedelta(days=epoch_days)
128
            where.append(BuildFarmJob.date_finished >= epoch)
11887.1.5 by Ian Booth
Next round
129
12425.2.1 by Ian Booth
Rework record sort order and add some tests
130
        # We include SourcePackageName directly in the query instead of just
131
        # selecting its id and fetching the objects later. This is because
132
        # SourcePackageName only has an id and and a name and we use the name
133
        # for the order by so there's no benefit in introducing another query
134
        # for eager fetching later. If SourcePackageName gets new attributes,
135
        # this can be re-evaluated.
11887.1.4 by Ian Booth
More coding
136
        result_set = store.using(*tables).find(
12425.2.1 by Ian Booth
Rework record sort order and add some tests
137
                (SourcePackageRecipe.id,
138
                    SourcePackageName,
11887.1.4 by Ian Booth
More coding
139
                    Max(BuildFarmJob.date_finished),
140
                    ),
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
141
                *where
11887.1.4 by Ian Booth
More coding
142
            ).group_by(
12425.2.1 by Ian Booth
Rework record sort order and add some tests
143
                SourcePackageRecipe.id,
144
                SourcePackageName,
11887.1.4 by Ian Booth
More coding
145
            ).order_by(
12425.2.1 by Ian Booth
Rework record sort order and add some tests
146
                SourcePackageName.name,
12425.1.2 by Ian Booth
Quick stab at improving daily recipe builds query
147
                Desc(Max(BuildFarmJob.date_finished)),
148
            )
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
149
150
        def _makeRecipeBuildRecord(values):
12425.2.1 by Ian Booth
Rework record sort order and add some tests
151
            (recipe_id, sourcepackagename, date_finished) = values
152
            recipe = store.get(SourcePackageRecipe, recipe_id)
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
153
            return RecipeBuildRecord(
12425.2.1 by Ian Booth
Rework record sort order and add some tests
154
                sourcepackagename, recipe.owner,
12425.1.2 by Ian Booth
Quick stab at improving daily recipe builds query
155
                recipe.daily_build_archive, recipe,
11887.1.9 by Ian Booth
Fix source package name lins in view, improve tests
156
                date_finished)
157
12425.2.1 by Ian Booth
Rework record sort order and add some tests
158
        to_recipes_ids = lambda rows: [row[0] for row in rows]
159
160
        def eager_load_recipes(recipe_ids):
161
            if not recipe_ids:
162
                return []
163
            return list(store.find(
164
                SourcePackageRecipe,
165
                SourcePackageRecipe.id.is_in(recipe_ids)))
12425.1.2 by Ian Booth
Quick stab at improving daily recipe builds query
166
167
        def eager_load_owners(recipes):
168
            owner_ids = set(recipe.owner_id for recipe in recipes)
169
            owner_ids.discard(None)
170
            if not owner_ids:
171
                return
172
            list(store.find(Person, Person.id.is_in(owner_ids)))
173
174
        def eager_load_archives(recipes):
175
            archive_ids = set(
176
            recipe.daily_build_archive_id for recipe in recipes)
177
            archive_ids.discard(None)
178
            if not archive_ids:
179
                return
180
            list(store.find(Archive, Archive.id.is_in(archive_ids)))
181
182
        def _prefetchRecipeBuildData(rows):
12425.2.1 by Ian Booth
Rework record sort order and add some tests
183
            recipe_ids = set(to_recipes_ids(rows))
184
            recipe_ids.discard(None)
185
            recipes = eager_load_recipes(recipe_ids)
12425.1.2 by Ian Booth
Quick stab at improving daily recipe builds query
186
            eager_load_owners(recipes)
187
            eager_load_archives(recipes)
188
11887.1.4 by Ian Booth
More coding
189
        return RecipeBuildRecordResultSet(
12425.1.2 by Ian Booth
Quick stab at improving daily recipe builds query
190
            result_set, _makeRecipeBuildRecord,
191
            pre_iter_hook=_prefetchRecipeBuildData)
11887.1.4 by Ian Booth
More coding
192
11887.1.5 by Ian Booth
Next round
193
11887.1.4 by Ian Booth
More coding
194
class RecipeBuildRecordResultSet(DecoratedResultSet):
11887.1.18 by Ian Booth
Fixes as per code review
195
    """A ResultSet which can count() queries with group by."""
11887.1.2 by Ian Booth
Initial coding
196
197
    def count(self, expr=Undef, distinct=True):
11887.1.4 by Ian Booth
More coding
198
        """This count() knows how to handle result sets with group by."""
199
11887.1.2 by Ian Booth
Initial coding
200
        # We don't support distinct=False for this result set
201
        select = Select(
11887.1.8 by Ian Booth
Improve count() for result set
202
            columns=CountDistinct(self.result_set._group_by),
14575.1.1 by Jeroen Vermeulen
Lint.
203
            tables=self.result_set._tables,
204
            where=self.result_set._where,
11887.1.2 by Ian Booth
Initial coding
205
            )
11887.1.4 by Ian Booth
More coding
206
        result = self.result_set._store.execute(select)
11887.1.2 by Ian Booth
Initial coding
207
        return result.get_one()[0]