Merge lp:~cjwatson/launchpad/ftparchive-release-contents into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 17407
Proposed branch: lp:~cjwatson/launchpad/ftparchive-release-contents
Merge into: lp:launchpad
Diff against target: 292 lines (+98/-25)
6 files modified
lib/lp/archivepublisher/publishing.py (+8/-2)
lib/lp/archivepublisher/scripts/publish_ftpmaster.py (+24/-12)
lib/lp/archivepublisher/scripts/publishdistro.py (+19/-6)
lib/lp/archivepublisher/tests/test_generate_contents_files.py (+17/-3)
lib/lp/archivepublisher/tests/test_publish_ftpmaster.py (+2/-1)
lib/lp/archivepublisher/tests/test_publisher.py (+28/-1)
To merge this branch: bzr merge lp:~cjwatson/launchpad/ftparchive-release-contents
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+253415@code.launchpad.net

Commit message

List Contents-* in Release files if available.

Description of the change

List Contents-* in Release files if available.

This entailed a little refactoring. We have to make sure that updated Contents files are in place before writing Release files, and we have to make sure that if Contents files change then we republish the suite even if there's nothing else to do.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)
Revision history for this message
Colin Watson (cjwatson) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/archivepublisher/publishing.py'
2--- lib/lp/archivepublisher/publishing.py 2014-10-31 12:48:39 +0000
3+++ lib/lp/archivepublisher/publishing.py 2015-03-18 18:07:07 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2009-2014 Canonical Ltd. This software is licensed under the
6+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 __all__ = [
10@@ -808,6 +808,7 @@
11 # and pocket, don't!
12 return
13
14+ suite = distroseries.getSuite(pocket)
15 all_components = [
16 comp.name for comp in
17 self.archive.getComponentsForSeries(distroseries)]
18@@ -822,6 +823,12 @@
19 distroseries, pocket, component, architecture, all_files)
20 self._writeSuiteI18n(
21 distroseries, pocket, component, all_files)
22+ for architecture in all_architectures:
23+ for contents_path in get_suffixed_indices(
24+ 'Contents-' + architecture):
25+ if os.path.exists(os.path.join(
26+ self._config.distsroot, suite, contents_path)):
27+ all_files.add(contents_path)
28
29 drsummary = "%s %s " % (self.distro.displayname,
30 distroseries.displayname)
31@@ -830,7 +837,6 @@
32 else:
33 drsummary += pocket.name.capitalize()
34
35- suite = distroseries.getSuite(pocket)
36 release_file = Release()
37 release_file["Origin"] = self._getOrigin()
38 release_file["Label"] = self._getLabel()
39
40=== modified file 'lib/lp/archivepublisher/scripts/publish_ftpmaster.py'
41--- lib/lp/archivepublisher/scripts/publish_ftpmaster.py 2015-03-06 07:50:53 +0000
42+++ lib/lp/archivepublisher/scripts/publish_ftpmaster.py 2015-03-18 18:07:07 +0000
43@@ -429,12 +429,14 @@
44 publish_distro.main()
45
46 def publishDistroArchive(self, distribution, archive,
47- security_suites=None):
48+ security_suites=None, updated_suites=[]):
49 """Publish the results for an archive.
50
51 :param archive: Archive to publish.
52 :param security_suites: An optional list of suites to restrict
53 the publishing to.
54+ :param updated_suites: An optional list of archive/suite pairs that
55+ have been updated out of band and should be republished.
56 """
57 purpose = archive.purpose
58 archive_config = self.configs[distribution][purpose]
59@@ -449,6 +451,9 @@
60 arguments = ['-R', temporary_dists]
61 if archive.purpose == ArchivePurpose.PARTNER:
62 arguments.append('--partner')
63+ for updated_archive, updated_suite in updated_suites:
64+ if archive == updated_archive:
65+ arguments.extend(['--dirty-suite', updated_suite])
66
67 os.rename(get_backup_dists(archive_config), temporary_dists)
68 try:
69@@ -544,18 +549,19 @@
70 security_suites=security_suites)
71 return True
72
73- def publishDistroUploads(self, distribution):
74+ def publishDistroUploads(self, distribution, updated_suites=[]):
75 """Publish the distro's complete uploads."""
76 self.logger.debug("Full publication. This may take some time.")
77 for archive in get_publishable_archives(distribution):
78 if archive.purpose in self.configs[distribution]:
79 # This, for the main archive, is where the script spends
80 # most of its time.
81- self.publishDistroArchive(distribution, archive)
82+ self.publishDistroArchive(
83+ distribution, archive, updated_suites=updated_suites)
84
85- def updateContentsFile(self, distribution, suite, arch):
86+ def updateContentsFile(self, archive, distribution, suite, arch):
87 """Update a single Contents file if necessary."""
88- config = self.configs[distribution][ArchivePurpose.PRIMARY]
89+ config = self.configs[distribution][archive.purpose]
90 backup_dists = get_backup_dists(config)
91 content_dists = os.path.join(
92 config.distroroot, "contents-generation", distribution.name,
93@@ -573,11 +579,16 @@
94
95 def updateContentsFiles(self, distribution):
96 """Pick up updated Contents files if necessary."""
97- for series in distribution.getNonObsoleteSeries():
98- for pocket in PackagePublishingPocket.items:
99- suite = series.getSuite(pocket)
100- for arch in series.enabled_architectures:
101- self.updateContentsFile(distribution, suite, arch)
102+ updated_suites = []
103+ for archive in get_publishable_archives(distribution):
104+ for series in distribution.getNonObsoleteSeries():
105+ for pocket in PackagePublishingPocket.items:
106+ suite = series.getSuite(pocket)
107+ for arch in series.enabled_architectures:
108+ self.updateContentsFile(
109+ archive, distribution, suite, arch)
110+ updated_suites.append((archive, suite))
111+ return updated_suites
112
113 def publish(self, distribution, security_only=False):
114 """Do the main publishing work.
115@@ -594,8 +605,9 @@
116 if security_only:
117 has_published = self.publishSecurityUploads(distribution)
118 else:
119- self.publishDistroUploads(distribution)
120- self.updateContentsFiles(distribution)
121+ updated_suites = self.updateContentsFiles(distribution)
122+ self.publishDistroUploads(
123+ distribution, updated_suites=updated_suites)
124 # Let's assume the main archive is always modified
125 has_published = True
126
127
128=== modified file 'lib/lp/archivepublisher/scripts/publishdistro.py'
129--- lib/lp/archivepublisher/scripts/publishdistro.py 2014-08-09 19:45:00 +0000
130+++ lib/lp/archivepublisher/scripts/publishdistro.py 2015-03-18 18:07:07 +0000
131@@ -1,4 +1,4 @@
132-# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
133+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
134 # GNU Affero General Public License version 3 (see the file LICENSE).
135
136 """Publisher script class."""
137@@ -70,6 +70,11 @@
138 type='string', default=[], help='The suite to publish')
139
140 self.parser.add_option(
141+ "--dirty-suite", metavar="SUITE", dest="dirty_suites",
142+ action="append", default=[],
143+ help="Consider this suite dirty regardless of publications.")
144+
145+ self.parser.add_option(
146 "-R", "--distsroot", dest="distsroot", metavar="SUFFIX",
147 default=None,
148 help=(
149@@ -174,19 +179,21 @@
150 """Find the named `suite` in the selected `Distribution`.
151
152 :param suite: The suite name to look for.
153- :return: A tuple of distroseries name and pocket.
154+ :return: A tuple of distroseries and pocket.
155 """
156 try:
157 series, pocket = distribution.getDistroSeriesAndPocket(suite)
158 except NotFoundError as e:
159 raise OptionValueError(e)
160- return series.name, pocket
161+ return series, pocket
162
163 def findAllowedSuites(self, distribution):
164 """Find the selected suite(s)."""
165- return set([
166- self.findSuite(distribution, suite)
167- for suite in self.options.suite])
168+ suites = set()
169+ for suite in self.options.suite:
170+ series, pocket = self.findSuite(distribution, suite)
171+ suites.add((series.name, pocket))
172+ return suites
173
174 def getCopyArchives(self, distribution):
175 """Find copy archives for the selected distribution."""
176@@ -285,6 +292,12 @@
177 if archive.status == ArchiveStatus.DELETING:
178 work_done = self.deleteArchive(archive, publisher)
179 elif archive.publish:
180+ for suite in self.options.dirty_suites:
181+ distroseries, pocket = self.findSuite(
182+ distribution, suite)
183+ if not publisher.cannotModifySuite(
184+ distroseries, pocket):
185+ publisher.markPocketDirty(distroseries, pocket)
186 self.publishArchive(archive, publisher)
187 work_done = True
188 else:
189
190=== modified file 'lib/lp/archivepublisher/tests/test_generate_contents_files.py'
191--- lib/lp/archivepublisher/tests/test_generate_contents_files.py 2015-02-13 10:33:30 +0000
192+++ lib/lp/archivepublisher/tests/test_generate_contents_files.py 2015-03-18 18:07:07 +0000
193@@ -5,6 +5,7 @@
194
195 __metaclass__ = type
196
197+import hashlib
198 from optparse import OptionValueError
199 import os
200
201@@ -262,7 +263,8 @@
202
203 def test_main(self):
204 # If run end-to-end, the script generates Contents.gz files, and a
205- # following publisher run will put those files in their final place.
206+ # following publisher run will put those files in their final place
207+ # and include them in the Release file.
208 distro = self.makeDistro()
209 distroseries = self.factory.makeDistroSeries(distribution=distro)
210 processor = self.factory.makeProcessor()
211@@ -287,9 +289,21 @@
212 publisher_script.txn = self.layer.txn
213 publisher_script.logger = DevNullLogger()
214 publisher_script.main()
215- self.assertTrue(file_exists(os.path.join(
216+ contents_path = os.path.join(
217 script.config.distsroot, suite,
218- "Contents-%s.gz" % das.architecturetag)))
219+ "Contents-%s.gz" % das.architecturetag)
220+ self.assertTrue(file_exists(contents_path))
221+ with open(contents_path, "rb") as contents_file:
222+ contents_bytes = contents_file.read()
223+ release_path = os.path.join(script.config.distsroot, suite, "Release")
224+ self.assertTrue(file_exists(release_path))
225+ with open(release_path) as release_file:
226+ release_lines = release_file.readlines()
227+ self.assertIn(
228+ " %s %16s Contents-%s.gz\n" % (
229+ hashlib.md5(contents_bytes).hexdigest(), len(contents_bytes),
230+ das.architecturetag),
231+ release_lines)
232
233 def test_run_script(self):
234 # The script will run stand-alone.
235
236=== modified file 'lib/lp/archivepublisher/tests/test_publish_ftpmaster.py'
237--- lib/lp/archivepublisher/tests/test_publish_ftpmaster.py 2015-02-13 10:33:30 +0000
238+++ lib/lp/archivepublisher/tests/test_publish_ftpmaster.py 2015-03-18 18:07:07 +0000
239@@ -862,7 +862,8 @@
240 os.makedirs(content_suite)
241 write_marker_file(
242 [content_suite, "%s-staged.gz" % contents_filename], "Contents")
243- script.updateContentsFile(distro, distroseries.name, das)
244+ script.updateContentsFile(
245+ distro.main_archive, distro, distroseries.name, das)
246 self.assertEqual(
247 "Contents",
248 read_marker_file([backup_suite, "%s.gz" % contents_filename]))
249
250=== modified file 'lib/lp/archivepublisher/tests/test_publisher.py'
251--- lib/lp/archivepublisher/tests/test_publisher.py 2014-10-30 13:04:31 +0000
252+++ lib/lp/archivepublisher/tests/test_publisher.py 2015-03-18 18:07:07 +0000
253@@ -1,4 +1,4 @@
254-# Copyright 2009-2014 Canonical Ltd. This software is licensed under the
255+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
256 # GNU Affero General Public License version 3 (see the file LICENSE).
257
258 """Tests for publisher class."""
259@@ -1820,6 +1820,33 @@
260 for component in components:
261 self.assertIn(component + '/i18n/Translation-en.bz2', content)
262
263+ def testReleaseFileForContents(self):
264+ """Test Release file writing for Contents files."""
265+ publisher = Publisher(
266+ self.logger, self.config, self.disk_pool,
267+ self.ubuntutest.main_archive)
268+
269+ # Put a Contents file in place, and force the publisher to republish
270+ # that suite.
271+ series_path = os.path.join(self.config.distsroot, 'breezy-autotest')
272+ contents_path = os.path.join(series_path, 'Contents-i386.gz')
273+ os.makedirs(os.path.dirname(contents_path))
274+ with gzip.GzipFile(contents_path, 'wb'):
275+ pass
276+ publisher.markPocketDirty(
277+ self.ubuntutest.getSeries('breezy-autotest'),
278+ PackagePublishingPocket.RELEASE)
279+
280+ publisher.A_publish(False)
281+ publisher.C_doFTPArchive(False)
282+ publisher.D_writeReleaseFiles(False)
283+
284+ # The Contents file is listed correctly in Release.
285+ release = self.parseRelease(os.path.join(series_path, 'Release'))
286+ with open(contents_path, "rb") as contents_file:
287+ self.assertReleaseContentsMatch(
288+ release, 'Contents-i386.gz', contents_file.read())
289+
290 def testCreateSeriesAliasesNoAlias(self):
291 """createSeriesAliases has nothing to do by default."""
292 publisher = Publisher(