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 (community) 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
=== modified file 'lib/lp/archiveuploader/livefsupload.py'
--- lib/lp/archiveuploader/livefsupload.py 2018-05-06 08:52:34 +0000
+++ lib/lp/archiveuploader/livefsupload.py 2019-08-29 04:14:54 +0000
@@ -1,4 +1,4 @@
1# Copyright 2014-2018 Canonical Ltd. This software is licensed under the1# Copyright 2014-2019 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Process a live filesystem upload."""4"""Process a live filesystem upload."""
@@ -47,7 +47,7 @@
47 livefs_file, os.stat(livefs_path).st_size,47 livefs_file, os.stat(livefs_path).st_size,
48 open(livefs_path, "rb"),48 open(livefs_path, "rb"),
49 filenameToContentType(livefs_path),49 filenameToContentType(livefs_path),
50 restricted=build.is_private)50 restricted=build.is_private, allow_zero_length=True)
51 build.addFile(libraryfile)51 build.addFile(libraryfile)
5252
53 # The master verifies the status to confirm successful upload.53 # The master verifies the status to confirm successful upload.
5454
=== modified file 'lib/lp/archiveuploader/tests/test_livefsupload.py'
--- lib/lp/archiveuploader/tests/test_livefsupload.py 2019-05-24 11:10:38 +0000
+++ lib/lp/archiveuploader/tests/test_livefsupload.py 2019-08-29 04:14:54 +0000
@@ -67,3 +67,21 @@
67 "LiveFS upload failed\nGot: %s" % self.log.getLogBuffer())67 "LiveFS upload failed\nGot: %s" % self.log.getLogBuffer())
68 self.assertEqual(BuildStatus.FULLYBUILT, self.build.status)68 self.assertEqual(BuildStatus.FULLYBUILT, self.build.status)
69 self.assertTrue(self.build.verifySuccessfulUpload())69 self.assertTrue(self.build.verifySuccessfulUpload())
70
71 def test_empty_file(self):
72 # The upload processor can cope with empty build artifacts. (LiveFS
73 # is quite a general method and can include a variety of artifacts:
74 # in particular it often includes an additional log file which can
75 # be empty.)
76 self.assertFalse(self.build.verifySuccessfulUpload())
77 upload_dir = os.path.join(
78 self.incoming_folder, "test", str(self.build.id), "ubuntu")
79 write_file(os.path.join(upload_dir, "livecd.magic-proxy.log"), b"")
80 handler = UploadHandler.forProcessor(
81 self.uploadprocessor, self.incoming_folder, "test", self.build)
82 result = handler.processLiveFS(self.log)
83 self.assertEqual(
84 UploadStatusEnum.ACCEPTED, result,
85 "LiveFS upload failed\nGot: %s" % self.log.getLogBuffer())
86 self.assertEqual(BuildStatus.FULLYBUILT, self.build.status)
87 self.assertTrue(self.build.verifySuccessfulUpload())
7088
=== modified file 'lib/lp/buildmaster/interactor.py'
--- lib/lp/buildmaster/interactor.py 2019-06-19 09:50:07 +0000
+++ lib/lp/buildmaster/interactor.py 2019-08-29 04:14:54 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2018 Canonical Ltd. This software is licensed under the1# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
@@ -62,16 +62,14 @@
62 self.finished = finished62 self.finished = finished
63 if isinstance(file_to_write, (bytes, unicode)):63 if isinstance(file_to_write, (bytes, unicode)):
64 self.filename = file_to_write64 self.filename = file_to_write
65 self.file = None65 self.file = tempfile.NamedTemporaryFile(
66 mode="wb", prefix=os.path.basename(self.filename) + "_",
67 dir=os.path.dirname(self.filename), delete=False)
66 else:68 else:
67 self.filename = None69 self.filename = None
68 self.file = file_to_write70 self.file = file_to_write
6971
70 def dataReceived(self, data):72 def dataReceived(self, data):
71 if self.file is None:
72 self.file = tempfile.NamedTemporaryFile(
73 mode="wb", prefix=os.path.basename(self.filename) + "_",
74 dir=os.path.dirname(self.filename), delete=False)
75 try:73 try:
76 self.file.write(data)74 self.file.write(data)
77 except IOError:75 except IOError:
7876
=== modified file 'lib/lp/buildmaster/tests/mock_slaves.py'
--- lib/lp/buildmaster/tests/mock_slaves.py 2019-07-02 15:43:07 +0000
+++ lib/lp/buildmaster/tests/mock_slaves.py 2019-08-29 04:14:54 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2018 Canonical Ltd. This software is licensed under the1# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Mock Build objects for tests soyuz buildd-system."""4"""Mock Build objects for tests soyuz buildd-system."""
@@ -316,18 +316,18 @@
316 self.base_url, 'vmhost', config.builddmaster.socket_timeout,316 self.base_url, 'vmhost', config.builddmaster.socket_timeout,
317 reactor=reactor, proxy=proxy, pool=pool)317 reactor=reactor, proxy=proxy, pool=pool)
318318
319 def makeCacheFile(self, tachandler, filename):319 def makeCacheFile(self, tachandler, filename, contents=b'something'):
320 """Make a cache file available on the remote slave.320 """Make a cache file available on the remote slave.
321321
322 :param tachandler: The TacTestSetup object used to start the remote322 :param tachandler: The TacTestSetup object used to start the remote
323 slave.323 slave.
324 :param filename: The name of the file to create in the file cache324 :param filename: The name of the file to create in the file cache
325 area.325 area.
326 :param contents: Bytes to write to the file.
326 """327 """
327 path = os.path.join(tachandler.root, 'filecache', filename)328 path = os.path.join(tachandler.root, 'filecache', filename)
328 fd = open(path, 'w')329 with open(path, 'wb') as fd:
329 fd.write('something')330 fd.write(contents)
330 fd.close()
331 self.addCleanup(os.unlink, path)331 self.addCleanup(os.unlink, path)
332332
333 def triggerGoodBuild(self, slave, build_id=None):333 def triggerGoodBuild(self, slave, build_id=None):
334334
=== modified file 'lib/lp/buildmaster/tests/test_interactor.py'
--- lib/lp/buildmaster/tests/test_interactor.py 2018-07-01 21:56:44 +0000
+++ lib/lp/buildmaster/tests/test_interactor.py 2019-08-29 04:14:54 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2018 Canonical Ltd. This software is licensed under the1# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Test BuilderInteractor features."""4"""Test BuilderInteractor features."""
@@ -10,6 +10,7 @@
10 'MockBuilderFactory',10 'MockBuilderFactory',
11 ]11 ]
1212
13import hashlib
13import os14import os
14import signal15import signal
15import tempfile16import tempfile
@@ -884,3 +885,17 @@
884 with open(temp_name) as f:885 with open(temp_name) as f:
885 self.assertEqual('content', f.read())886 self.assertEqual('content', f.read())
886 yield slave.pool.closeCachedConnections()887 yield slave.pool.closeCachedConnections()
888
889 @defer.inlineCallbacks
890 def test_getFiles_with_empty_file(self):
891 # getFiles works with zero-length files.
892 tachandler = self.slave_helper.getServerSlave()
893 slave = self.slave_helper.getClientSlave()
894 temp_fd, temp_name = tempfile.mkstemp()
895 self.addCleanup(os.remove, temp_name)
896 empty_sha1 = hashlib.sha1(b'').hexdigest()
897 self.slave_helper.makeCacheFile(tachandler, empty_sha1, contents=b'')
898 yield slave.getFiles([(empty_sha1, temp_name)])
899 with open(temp_name) as f:
900 self.assertEqual(b'', f.read())
901 yield slave.pool.closeCachedConnections()