Merge lp:~cjwatson/launchpad/empty-build-artifacts into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 19038
Proposed branch: lp:~cjwatson/launchpad/empty-build-artifacts
Merge into: lp:launchpad
Diff against target: 143 lines (+45/-14)
5 files modified
lib/lp/archiveuploader/livefsupload.py (+2/-2)
lib/lp/archiveuploader/tests/test_livefsupload.py (+18/-0)
lib/lp/buildmaster/interactor.py (+4/-6)
lib/lp/buildmaster/tests/mock_slaves.py (+5/-5)
lib/lp/buildmaster/tests/test_interactor.py (+16/-1)
To merge this branch: bzr merge lp:~cjwatson/launchpad/empty-build-artifacts
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+371975@code.launchpad.net

Commit message

Handle empty artifacts in livefs builds.

Description of the change

I also considered changing launchpad-buildd to avoid sending empty artifacts, but it seemed a bit arbitrary, and I think it makes sense for Launchpad to be more robust against this anyway.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/archiveuploader/livefsupload.py'
2--- lib/lp/archiveuploader/livefsupload.py 2018-05-06 08:52:34 +0000
3+++ lib/lp/archiveuploader/livefsupload.py 2019-08-29 04:14:54 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2014-2018 Canonical Ltd. This software is licensed under the
6+# Copyright 2014-2019 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Process a live filesystem upload."""
10@@ -47,7 +47,7 @@
11 livefs_file, os.stat(livefs_path).st_size,
12 open(livefs_path, "rb"),
13 filenameToContentType(livefs_path),
14- restricted=build.is_private)
15+ restricted=build.is_private, allow_zero_length=True)
16 build.addFile(libraryfile)
17
18 # The master verifies the status to confirm successful upload.
19
20=== modified file 'lib/lp/archiveuploader/tests/test_livefsupload.py'
21--- lib/lp/archiveuploader/tests/test_livefsupload.py 2019-05-24 11:10:38 +0000
22+++ lib/lp/archiveuploader/tests/test_livefsupload.py 2019-08-29 04:14:54 +0000
23@@ -67,3 +67,21 @@
24 "LiveFS upload failed\nGot: %s" % self.log.getLogBuffer())
25 self.assertEqual(BuildStatus.FULLYBUILT, self.build.status)
26 self.assertTrue(self.build.verifySuccessfulUpload())
27+
28+ def test_empty_file(self):
29+ # The upload processor can cope with empty build artifacts. (LiveFS
30+ # is quite a general method and can include a variety of artifacts:
31+ # in particular it often includes an additional log file which can
32+ # be empty.)
33+ self.assertFalse(self.build.verifySuccessfulUpload())
34+ upload_dir = os.path.join(
35+ self.incoming_folder, "test", str(self.build.id), "ubuntu")
36+ write_file(os.path.join(upload_dir, "livecd.magic-proxy.log"), b"")
37+ handler = UploadHandler.forProcessor(
38+ self.uploadprocessor, self.incoming_folder, "test", self.build)
39+ result = handler.processLiveFS(self.log)
40+ self.assertEqual(
41+ UploadStatusEnum.ACCEPTED, result,
42+ "LiveFS upload failed\nGot: %s" % self.log.getLogBuffer())
43+ self.assertEqual(BuildStatus.FULLYBUILT, self.build.status)
44+ self.assertTrue(self.build.verifySuccessfulUpload())
45
46=== modified file 'lib/lp/buildmaster/interactor.py'
47--- lib/lp/buildmaster/interactor.py 2019-06-19 09:50:07 +0000
48+++ lib/lp/buildmaster/interactor.py 2019-08-29 04:14:54 +0000
49@@ -1,4 +1,4 @@
50-# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
51+# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
52 # GNU Affero General Public License version 3 (see the file LICENSE).
53
54 __metaclass__ = type
55@@ -62,16 +62,14 @@
56 self.finished = finished
57 if isinstance(file_to_write, (bytes, unicode)):
58 self.filename = file_to_write
59- self.file = None
60+ self.file = tempfile.NamedTemporaryFile(
61+ mode="wb", prefix=os.path.basename(self.filename) + "_",
62+ dir=os.path.dirname(self.filename), delete=False)
63 else:
64 self.filename = None
65 self.file = file_to_write
66
67 def dataReceived(self, data):
68- if self.file is None:
69- self.file = tempfile.NamedTemporaryFile(
70- mode="wb", prefix=os.path.basename(self.filename) + "_",
71- dir=os.path.dirname(self.filename), delete=False)
72 try:
73 self.file.write(data)
74 except IOError:
75
76=== modified file 'lib/lp/buildmaster/tests/mock_slaves.py'
77--- lib/lp/buildmaster/tests/mock_slaves.py 2019-07-02 15:43:07 +0000
78+++ lib/lp/buildmaster/tests/mock_slaves.py 2019-08-29 04:14:54 +0000
79@@ -1,4 +1,4 @@
80-# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
81+# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
82 # GNU Affero General Public License version 3 (see the file LICENSE).
83
84 """Mock Build objects for tests soyuz buildd-system."""
85@@ -316,18 +316,18 @@
86 self.base_url, 'vmhost', config.builddmaster.socket_timeout,
87 reactor=reactor, proxy=proxy, pool=pool)
88
89- def makeCacheFile(self, tachandler, filename):
90+ def makeCacheFile(self, tachandler, filename, contents=b'something'):
91 """Make a cache file available on the remote slave.
92
93 :param tachandler: The TacTestSetup object used to start the remote
94 slave.
95 :param filename: The name of the file to create in the file cache
96 area.
97+ :param contents: Bytes to write to the file.
98 """
99 path = os.path.join(tachandler.root, 'filecache', filename)
100- fd = open(path, 'w')
101- fd.write('something')
102- fd.close()
103+ with open(path, 'wb') as fd:
104+ fd.write(contents)
105 self.addCleanup(os.unlink, path)
106
107 def triggerGoodBuild(self, slave, build_id=None):
108
109=== modified file 'lib/lp/buildmaster/tests/test_interactor.py'
110--- lib/lp/buildmaster/tests/test_interactor.py 2018-07-01 21:56:44 +0000
111+++ lib/lp/buildmaster/tests/test_interactor.py 2019-08-29 04:14:54 +0000
112@@ -1,4 +1,4 @@
113-# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
114+# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
115 # GNU Affero General Public License version 3 (see the file LICENSE).
116
117 """Test BuilderInteractor features."""
118@@ -10,6 +10,7 @@
119 'MockBuilderFactory',
120 ]
121
122+import hashlib
123 import os
124 import signal
125 import tempfile
126@@ -884,3 +885,17 @@
127 with open(temp_name) as f:
128 self.assertEqual('content', f.read())
129 yield slave.pool.closeCachedConnections()
130+
131+ @defer.inlineCallbacks
132+ def test_getFiles_with_empty_file(self):
133+ # getFiles works with zero-length files.
134+ tachandler = self.slave_helper.getServerSlave()
135+ slave = self.slave_helper.getClientSlave()
136+ temp_fd, temp_name = tempfile.mkstemp()
137+ self.addCleanup(os.remove, temp_name)
138+ empty_sha1 = hashlib.sha1(b'').hexdigest()
139+ self.slave_helper.makeCacheFile(tachandler, empty_sha1, contents=b'')
140+ yield slave.getFiles([(empty_sha1, temp_name)])
141+ with open(temp_name) as f:
142+ self.assertEqual(b'', f.read())
143+ yield slave.pool.closeCachedConnections()