From 186d6db4b28fd022a9456f83610bac2a5a1eb45e Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Mar 07 2017 17:22:32 +0000 Subject: Handle empty file upload --- diff --git a/koji/__init__.py b/koji/__init__.py index 2fdc531..33796e4 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -2458,7 +2458,6 @@ class ClientSession(object): if name is None: name = os.path.basename(localfile) self.logger.debug("Fast upload: %s to %s/%s", localfile, path, name) - size = os.stat(localfile).st_size fo = file(localfile, 'rb') ofs = 0 size = os.path.getsize(localfile) @@ -2467,11 +2466,14 @@ class ClientSession(object): callback(0, size, 0, 0, 0) problems = False full_chksum = util.adler32_constructor() + # cycle is need to run at least once (for empty files) + first_cycle = True while True: lap = time.time() chunk = fo.read(blocksize) - if not chunk: + if not chunk and not first_cycle: break + first_cycle = False result = self._callMethod('rawUpload', (chunk, ofs, path, name), {'overwrite':overwrite}) if self.retries > 1: problems = True @@ -2495,6 +2497,8 @@ class ClientSession(object): if problems: chk_opts['verify'] = 'adler32' result = self._callMethod('checkUpload', (path, name), chk_opts) + if result is None: + raise GenericError("File upload failed: %s/%s" % (path, name)) if int(result['size']) != ofs: raise GenericError("Uploaded file is wrong length: %s/%s, %s != %s" \ % (path, name, result['size'], ofs)) diff --git a/tests/test_client_session.py b/tests/test_client_session.py index 3931891..9d1cc3d 100644 --- a/tests/test_client_session.py +++ b/tests/test_client_session.py @@ -63,3 +63,96 @@ class TestClientSession(unittest.TestCase): ksession.new_session() my_rsession.close.assert_called() self.assertNotEqual(ksession.rsession, my_rsession) + +class TestFastUpload(unittest.TestCase): + @mock.patch('koji.compatrequests.Session') + @mock.patch('requests.Session') + @mock.patch('__builtin__.file') + @mock.patch('os.path.getsize') + def test_fastUpload(self, getsize_mock, file_mock, rsession, compat_session): + ksession = koji.ClientSession('http://koji.example.com/kojihub', {}) + + # without login (ActionNotAllowed) + ksession.logged_in = False + with self.assertRaises(koji.ActionNotAllowed): + ksession.fastUpload('nonexistent_file', 'target') + + # fake login + ksession.logged_in = True + ksession.sinfo = {} + ksession.callnum = 1 + ksession._callMethod = mock.MagicMock() + + # fail with nonexistent file (IOError) + file_mock.side_effect = IOError('mocked exception') + with self.assertRaises(IOError): + ksession.fastUpload('file', 'target') + + # inaccessible file, permissions (IOError) + file_mock.side_effect = IOError('mocked exception') + with self.assertRaises(IOError): + ksession.fastUpload('file', 'target') + + # upload empty file (success) + file_mock.side_effect = None + fileobj = mock.MagicMock() + fileobj.read.return_value = '' + file_mock.return_value = fileobj + ksession._callMethod.return_value = { + 'size': 0, + 'hexdigest': koji.util.adler32_constructor().hexdigest() + } + ksession.fastUpload('file', 'target') + + # upload regular file (success) + file_mock.side_effect = None + fileobj = mock.MagicMock() + fileobj.read.side_effect = ['123123', ''] + file_mock.return_value = fileobj + ksession._callMethod.reset_mock() + ksession._callMethod.side_effect = [ + {'size': 6, 'hexdigest': '041c012d'}, # rawUpload + {'size': 6, 'hexdigest': '041c012d'}, # checkUpload + ] + ksession.fastUpload('file', 'target', blocksize=1024) + + # change file size during upload (success) + file_mock.side_effect = None + fileobj = mock.MagicMock() + fileobj.read.side_effect = ['123123', ''] + file_mock.return_value = fileobj + getsize_mock.return_value = 123456 + ksession._callMethod.reset_mock() + ksession._callMethod.side_effect = [ + {'size': 6, 'hexdigest': '041c012d'}, # rawUpload + {'size': 6, 'hexdigest': '041c012d'}, # checkUpload + ] + ksession.fastUpload('file', 'target', blocksize=1024) + + # uploaded file is corrupted (length) (GenericError) + file_mock.side_effect = None + fileobj = mock.MagicMock() + fileobj.read.side_effect = ['123123', ''] + file_mock.return_value = fileobj + getsize_mock.return_value = 123456 + ksession._callMethod.reset_mock() + ksession._callMethod.side_effect = [ + {'size': 6, 'hexdigest': '041c012d'}, # rawUpload + {'size': 3, 'hexdigest': '041c012d'}, # checkUpload + ] + with self.assertRaises(koji.GenericError): + ksession.fastUpload('file', 'target', blocksize=1024) + + # uploaded file is corrupted (checksum) (GenericError) + file_mock.side_effect = None + fileobj = mock.MagicMock() + fileobj.read.side_effect = ['123123', ''] + file_mock.return_value = fileobj + getsize_mock.return_value = 123456 + ksession._callMethod.reset_mock() + ksession._callMethod.side_effect = [ + {'size': 6, 'hexdigest': '041c012d'}, # rawUpload + {'size': 3, 'hexdigest': 'deadbeef'}, # checkUpload + ] + with self.assertRaises(koji.GenericError): + ksession.fastUpload('file', 'target', blocksize=1024)