#343 Handle empty file upload
Merged 7 years ago by mikem. Opened 7 years ago by tkopecek.
tkopecek/koji issue342  into  master

file modified
+6 -2
@@ -2458,7 +2458,6 @@ 

          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 @@ 

              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 @@ 

          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))

@@ -63,3 +63,96 @@ 

          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)

Added a fix to cli _progress_callback here:
https://github.com/mikem23/koji-playground/commits/issue342

Let me know if that looks good and I'll merge

Commit 0d1ec40 fixes this pull-request

Pull-Request has been merged by mikem@redhat.com

7 years ago