def test_cleanup_all_temporary_files(tmpdir):
    opts = mock.MagicMock()
    opts.check_file_md5 = False
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 16
    lp = pathlib.Path(str(tmpdir.join('a')))
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)

    offsets, _ = d.next_offsets()
    data = b'0' * opts.chunk_size_bytes
    d.write_unchecked_data(offsets, data)
    assert len(d._unchecked_chunks) == 1
    d.cleanup_all_temporary_files()
    assert not d.final_path.exists()
    assert not d._unchecked_chunks[0]['ucc'].file_path.exists()

    lp = pathlib.Path(str(tmpdir.join('b')))
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)

    offsets, _ = d.next_offsets()
    data = b'0' * opts.chunk_size_bytes
    d.write_unchecked_hmac_data(offsets, data)
    assert len(d._unchecked_chunks) == 1
    d._unchecked_chunks[0]['ucc'].file_path.unlink()
    d.cleanup_all_temporary_files()
    assert not d.final_path.exists()
    assert not d._unchecked_chunks[0]['ucc'].file_path.exists()

    # go through except path
    d.cleanup_all_temporary_files()
    assert not d.final_path.exists()
def test_downloaddescriptor(tmpdir):
    lp = pathlib.Path(str(tmpdir.join('a')))

    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 1024
    ase._encryption = mock.MagicMock()
    with pytest.raises(RuntimeError):
        d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)

    ase._encryption.symmetric_key = b'123'
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    assert not d._allocated
    d._allocate_disk_space()

    assert d.entity == ase
    assert d.entity.is_encrypted
    assert not d.must_compute_md5
    assert d.hmac is not None
    assert d._total_chunks == 64
    assert d._offset == 0
    assert d.final_path == lp
    assert d._allocated
    assert d.final_path.stat().st_size == ase._size - 16

    d._allocate_disk_space()
    assert d._allocated

    d.final_path.unlink()
    ase._size = 32
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    d._allocate_disk_space()
    assert d._total_chunks == 2
    assert d._allocated
    assert d.final_path.stat().st_size == ase._size - 16

    d.final_path.unlink()
    ase._encryption = None
    ase._size = 1024
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    d._allocate_disk_space()
    assert d._allocated
    assert d.final_path.stat().st_size == ase._size

    # pre-existing file check
    opts.chunk_size_bytes = 0
    ase._size = 0
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    d._allocate_disk_space()
    assert d._total_chunks == 0
    assert d._allocated
    assert d.final_path.stat().st_size == ase._size
Exemple #3
0
def test_cleanup_temporary_files(tmpdir):
    lp = pathlib.Path(str(tmpdir.join('a')))
    opts = mock.MagicMock()
    opts.check_file_md5 = False
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 16
    dd = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    dd._allocate_disk_space()
    dd.cleanup_all_temporary_files = mock.MagicMock()
    dd.cleanup_all_temporary_files.side_effect = Exception
    d = ops.Downloader(mock.MagicMock(), mock.MagicMock(), mock.MagicMock())
    d._general_options.dry_run = False
    d._general_options.resume_file = pathlib.Path('abc')
    d._dd_map[0] = dd
    d._cleanup_temporary_files()
    assert dd.final_path.exists()

    lp = pathlib.Path(str(tmpdir.join('b')))
    opts = mock.MagicMock()
    opts.check_file_md5 = False
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 16
    dd = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    dd._allocate_disk_space()
    d = ops.Downloader(mock.MagicMock(), mock.MagicMock(), mock.MagicMock())
    d._general_options.dry_run = False
    d._general_options.resume_file = None
    d._dd_map[0] = dd
    d._cleanup_temporary_files()
    assert not dd.final_path.exists()

    lp = pathlib.Path(str(tmpdir.join('c')))
    opts = mock.MagicMock()
    opts.check_file_md5 = False
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 16
    dd = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    dd._allocate_disk_space()
    dd.cleanup_all_temporary_files = mock.MagicMock()
    dd.cleanup_all_temporary_files.side_effect = Exception
    d = ops.Downloader(mock.MagicMock(), mock.MagicMock(), mock.MagicMock())
    d._general_options.dry_run = False
    d._general_options.resume_file = None
    d._dd_map[0] = dd
    d._cleanup_temporary_files()
    assert dd.final_path.exists()
def test_mark_unchecked_chunk_decrypted():
    opts = mock.MagicMock()
    opts.check_file_md5 = False
    opts.chunk_size_bytes = 32
    ase = azmodels.StorageEntity('cont')
    ase._size = 32
    d = models.Descriptor(mock.MagicMock(), ase, opts, mock.MagicMock(), None)

    d._unchecked_chunks[0] = {'decrypted': False}

    d.mark_unchecked_chunk_decrypted(0)
    assert d._unchecked_chunks[0]
def test_set_final_path_view():
    lp = pathlib.Path('/local/path/abc.bxslice-0')

    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 1024
    ase._vio = mock.MagicMock()
    ase._vio.slice_id = 0
    ase._vio.total_size = 1024
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)

    total_size = d._set_final_path_view()
    assert total_size == ase._size
def test_operations(tmpdir):
    lp = pathlib.Path(str(tmpdir.join('a')))
    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 32

    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    d._outstanding_ops = 1
    d._unchecked_chunks = {0: None}
    assert not d.all_operations_completed

    d._outstanding_ops -= 1
    d._unchecked_chunks.pop(0)
    assert d.all_operations_completed
def test_write_data(tmpdir):
    lp = pathlib.Path(str(tmpdir.join('a')))

    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 32
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)

    offsets, _ = d.next_offsets()
    data = b'0' * ase._size
    d.write_data(offsets, data)

    assert d.final_path.exists()
    assert d.final_path.stat().st_size == len(data)
def test_update_resume_for_completed(tmpdir):
    resumefile = pathlib.Path(str(tmpdir.join('resume')))
    fp = pathlib.Path(str(tmpdir.join('fp')))
    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 32
    ase._name = 'blob'
    ase._client = mock.MagicMock()
    rmgr = rops.DownloadResumeManager(resumefile)
    d = models.Descriptor(fp, ase, opts, mock.MagicMock(), rmgr)
    offsets, _ = d.next_offsets()
    d._update_resume_for_completed()
    dr = rmgr.get_record(ase)
    assert dr.completed
def test_hmac_iv(tmpdir):
    lp = pathlib.Path(str(tmpdir.join('a')))

    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 256
    ase = azmodels.StorageEntity('cont')
    ase._size = 128
    ase._encryption = mock.MagicMock()
    ase._encryption.symmetric_key = b'123'
    ase._size = 128
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)

    iv = b'abc'
    d.hmac_iv(iv)
    assert d.hmac.update.call_count == 1
def test_downloaddescriptor_allocate_disk_space_via_seek(tmpdir):
    fp = pathlib.Path(str(tmpdir.join('fp')))
    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 256
    ase = azmodels.StorageEntity('cont')
    ase._size = 128
    ase._name = 'blob'
    d = models.Descriptor(fp, ase, opts, mock.MagicMock(), None)

    with mock.patch('os.posix_fallocate') as patched_fallocate:
        patched_fallocate.side_effect = [AttributeError()]
        d._allocate_disk_space()
        assert d._allocated
        assert fp.exists()
        assert fp.stat().st_size == ase._size
def test_restore_file_attributes(tmpdir):
    lp = pathlib.Path(str(tmpdir.join('a')))
    lp.touch(mode=0o666, exist_ok=False)
    lp.exists()

    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 32
    ase._fileattr = mock.MagicMock()
    ase._fileattr.mode = '0o100777'
    ase._fileattr.uid = 1000
    ase._fileattr.gid = 1000

    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    d._restore_file_attributes()
    stat = lp.stat()
    assert str(oct(stat.st_mode)).replace('o', '') == \
        ase._fileattr.mode.replace('o', '')
def test_write_unchecked_hmac_data(tmpdir):
    lp = pathlib.Path(str(tmpdir.join('a')))

    opts = mock.MagicMock()
    opts.check_file_md5 = False
    opts.chunk_size_bytes = 32
    ase = azmodels.StorageEntity('cont')
    ase._size = 32
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)

    offsets, _ = d.next_offsets()
    d.write_unchecked_hmac_data(offsets, b'0' * ase._size)

    assert offsets.chunk_num in d._unchecked_chunks
    ucc = d._unchecked_chunks[offsets.chunk_num]
    assert ucc['ucc'].data_len == ase._size
    assert ucc['ucc'].fd_start == offsets.fd_start
    assert ucc['ucc'].file_path != d.final_path
    assert ucc['ucc'].temp
    assert not ucc['decrypted']
Exemple #13
0
def test_process_download_descriptor_vio(tmpdir):
    with mock.patch(
            'blobxfer.models.download.Descriptor.all_operations_completed',
            new_callable=mock.PropertyMock) as patched_aoc:
        d = ops.Downloader(mock.MagicMock(), mock.MagicMock(),
                           mock.MagicMock())
        d._general_options.dry_run = False
        d._general_options.concurrency.transfer_threads = 1
        d._general_options.concurrency.disk_threads = 1
        opts = mock.MagicMock()
        opts.check_file_md5 = True
        opts.chunk_size_bytes = 16
        ase = azmodels.StorageEntity('cont')
        ase._mode = azmodels.StorageModes.File
        ase._size = 16
        ase._client = mock.MagicMock()
        ase._client.primary_endpoint = 'ep'
        ase._name = 'name'
        ase._vio = mock.MagicMock()
        ase._vio.total_slices = 2

        lp = pathlib.Path(str(tmpdir.join('b')))
        dd = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
        dd.next_offsets = mock.MagicMock()
        dd.next_offsets.return_value = (None, None)
        patched_aoc.return_value = True
        dd.finalize_file = mock.MagicMock()
        key = ops.Downloader.create_unique_transfer_operation_id(ase)
        d._transfer_set.add(key)
        d._dd_map[str(lp)] = mock.MagicMock()
        d._transfer_cc[dd.final_path] = mock.MagicMock()

        d._process_download_descriptor(dd)
        assert dd.finalize_file.call_count == 0

        d._transfer_set.add(key)
        d._dd_map[str(lp)] = mock.MagicMock()
        d._transfer_cc[dd.final_path] = mock.MagicMock()
        d._process_download_descriptor(dd)
        assert dd.finalize_file.call_count == 1
def test_restore_file_lmt(tmpdir):
    lp = pathlib.Path(str(tmpdir.join('a')))
    lp.touch(mode=0o666, exist_ok=False)
    lp.exists()

    ts = util.datetime_now() - datetime.timedelta(seconds=60)
    ts_posix = time.mktime(ts.timetuple())

    stat = lp.stat()
    assert stat.st_mtime != ts_posix

    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 16
    opts.restore_file_properties.lmt = True
    ase = azmodels.StorageEntity('cont')
    ase._size = 32
    ase._lmt = ts

    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    d._restore_file_lmt()
    stat = lp.stat()
    assert stat.st_mtime == ts_posix
Exemple #15
0
def test_worker_thread_transfer(
        patched_gbr, patched_gfr, patched_acdd, tmpdir):
    # test disk set > max set length
    with mock.patch(
            'blobxfer.operations.download.Downloader.termination_check',
            new_callable=mock.PropertyMock) as patched_tc:
        d = ops.Downloader(
            mock.MagicMock(), mock.MagicMock(), mock.MagicMock())
        d._general_options.dry_run = False
        d._process_download_descriptor = mock.MagicMock()
        d._general_options.concurrency.disk_threads = 1
        d._disk_set.add(0)
        d._disk_set.add(1)
        d._disk_set.add(2)
        d._disk_set.add(3)
        d._disk_set.add(4)

        patched_tc.side_effect = [False, True]
        d._worker_thread_transfer()
        assert d._process_download_descriptor.call_count == 0

    d = ops.Downloader(mock.MagicMock(), mock.MagicMock(), mock.MagicMock())
    d._general_options.dry_run = False
    d._process_download_descriptor = mock.MagicMock()
    d._download_terminate = True
    d._general_options.concurrency.transfer_threads = 1
    d._general_options.concurrency.disk_threads = 1
    d._worker_thread_transfer()
    assert d._process_download_descriptor.call_count == 0

    d._download_terminate = False
    d._all_remote_files_processed = True
    d._worker_thread_transfer()
    assert d._process_download_descriptor.call_count == 0

    with mock.patch(
            'blobxfer.operations.download.Downloader.termination_check',
            new_callable=mock.PropertyMock) as patched_tc:
        patched_tc.side_effect = [False, False, True]
        ase = azmodels.StorageEntity('cont')
        ase._size = 16
        ase._encryption = mock.MagicMock()
        ase._encryption.symmetric_key = b'abc'
        lp = pathlib.Path(str(tmpdir.join('exc')))
        opts = mock.MagicMock()
        opts.check_file_md5 = False
        opts.chunk_size_bytes = 16
        dd = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
        d._transfer_queue = mock.MagicMock()
        d._transfer_queue.get.side_effect = [queue.Empty, dd]
        d._process_download_descriptor = mock.MagicMock()
        d._process_download_descriptor.side_effect = RuntimeError('oops')
        d._worker_thread_transfer()
        assert len(d._exceptions) == 1
        assert d._process_download_descriptor.call_count == 1

    with mock.patch(
            'blobxfer.operations.download.Downloader.termination_check',
            new_callable=mock.PropertyMock) as patched_tc:
        with mock.patch(
                'blobxfer.models.download.Descriptor.'
                'all_operations_completed',
                new_callable=mock.PropertyMock) as patched_aoc:
            d = ops.Downloader(
                mock.MagicMock(), mock.MagicMock(), mock.MagicMock())
            d._general_options.dry_run = False
            d._general_options.concurrency.transfer_threads = 1
            d._general_options.concurrency.disk_threads = 1
            opts = mock.MagicMock()
            opts.check_file_md5 = False
            opts.chunk_size_bytes = 16
            ase = azmodels.StorageEntity('cont')
            ase._size = 16
            ase._client = mock.MagicMock()
            ase._client.primary_endpoint = 'ep'
            ase._name = 'name'
            ase._vio = None
            key = ops.Downloader.create_unique_transfer_operation_id(ase)
            ase._encryption = mock.MagicMock()
            ase._encryption.symmetric_key = b'abc'
            lp = pathlib.Path(str(tmpdir.join('a')))
            dd = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
            dd.next_offsets = mock.MagicMock(
                side_effect=[(None, 1), (None, 2)])
            dd.finalize_integrity = mock.MagicMock()
            dd.finalize_file = mock.MagicMock()
            dd.perform_chunked_integrity_check = mock.MagicMock()
            dd.all_operations_completed.side_effect = [False, True]
            patched_aoc.side_effect = [False, True]
            patched_tc.side_effect = [False, False, False, True]
            d._dd_map[str(lp)] = dd
            d._transfer_set.add(key)
            d._transfer_queue = mock.MagicMock()
            d._transfer_queue.get.side_effect = [queue.Empty, dd, dd]
            d._worker_thread_transfer()
            assert str(lp) not in d._dd_map
            assert dd.finalize_file.call_count == 1
            assert d._download_sofar == 1
            assert d._download_bytes_sofar == 3

    with mock.patch(
            'blobxfer.operations.download.Downloader.termination_check',
            new_callable=mock.PropertyMock) as patched_tc:
        d = ops.Downloader(
            mock.MagicMock(), mock.MagicMock(), mock.MagicMock())
        d._general_options.dry_run = False
        d._general_options.concurrency.transfer_threads = 1
        d._general_options.concurrency.disk_threads = 1
        opts = mock.MagicMock()
        opts.check_file_md5 = True
        opts.chunk_size_bytes = 16
        ase = azmodels.StorageEntity('cont')
        ase._mode = azmodels.StorageModes.File
        ase._size = 16
        ase._client = mock.MagicMock()
        ase._client.primary_endpoint = 'ep'
        ase._name = 'name'
        ase._vio = None
        key = ops.Downloader.create_unique_transfer_operation_id(ase)
        patched_gfr.return_value = b'0' * ase._size
        lp = pathlib.Path(str(tmpdir.join('b')))
        dd = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
        dd.finalize_file = mock.MagicMock()
        dd.perform_chunked_integrity_check = mock.MagicMock()
        d._dd_map[str(lp)] = mock.MagicMock()
        d._transfer_cc[dd.entity.path] = 0
        d._transfer_set.add(key)
        d._transfer_queue = mock.MagicMock()
        d._transfer_queue.get.side_effect = [dd]
        patched_tc.side_effect = [False, True]
        d._spec.options.max_single_object_concurrency = 0
        d._worker_thread_transfer()
        assert len(d._disk_set) == 1
        a, b, c = d._disk_queue.get()
        d._process_data(a, b, c)
        assert dd.perform_chunked_integrity_check.call_count == 1

    with mock.patch(
            'blobxfer.operations.download.Downloader.termination_check',
            new_callable=mock.PropertyMock) as patched_tc:
        d = ops.Downloader(
            mock.MagicMock(), mock.MagicMock(), mock.MagicMock())
        d._general_options.dry_run = False
        d._general_options.concurrency.transfer_threads = 1
        d._general_options.concurrency.disk_threads = 1
        d._spec.options.max_single_object_concurrency = 8
        opts = mock.MagicMock()
        opts.check_file_md5 = False
        opts.chunk_size_bytes = 16
        ase = azmodels.StorageEntity('cont')
        ase._mode = azmodels.StorageModes.Auto
        ase._size = 32
        ase._encryption = mock.MagicMock()
        ase._encryption.symmetric_key = b'abc'
        ase._encryption.content_encryption_iv = b'0' * 16
        ase._client = mock.MagicMock()
        ase._client.primary_endpoint = 'ep'
        ase._name = 'name'
        ase._vio = None
        key = ops.Downloader.create_unique_transfer_operation_id(ase)
        patched_gfr.return_value = b'0' * ase._size
        lp = pathlib.Path(str(tmpdir.join('c')))
        dd = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
        dd.finalize_file = mock.MagicMock()
        dd.write_unchecked_hmac_data = mock.MagicMock()
        dd.perform_chunked_integrity_check = mock.MagicMock()
        d._crypto_offload = mock.MagicMock()
        d._crypto_offload.add_decrypt_chunk = mock.MagicMock()
        d._dd_map[str(lp)] = dd
        d._transfer_cc[dd.entity.path] = 0
        d._transfer_set.add(key)
        d._transfer_queue = mock.MagicMock()
        d._transfer_queue.get.side_effect = [dd]
        patched_tc.side_effect = [False, True]
        d._worker_thread_transfer()
        assert len(d._disk_set) == 1
        a, b, c = d._disk_queue.get()
        d._process_data(a, b, c)
        assert d._crypto_offload.add_decrypt_chunk.call_count == 1
        assert dd.write_unchecked_hmac_data.call_count == 1

    with mock.patch(
            'blobxfer.operations.download.Downloader.termination_check',
            new_callable=mock.PropertyMock) as patched_tc:
        d = ops.Downloader(
            mock.MagicMock(), mock.MagicMock(), mock.MagicMock())
        d._general_options.dry_run = False
        d._general_options.concurrency.crypto_processes = 0
        d._general_options.concurrency.transfer_threads = 1
        d._general_options.concurrency.disk_threads = 1
        d._spec.options.max_single_object_concurrency = 8
        opts = mock.MagicMock()
        opts.check_file_md5 = False
        opts.chunk_size_bytes = 16
        ase = azmodels.StorageEntity('cont')
        ase._mode = azmodels.StorageModes.Auto
        ase._size = 32
        ase._encryption = mock.MagicMock()
        ase._encryption.symmetric_key = b'abc'
        ase._encryption.content_encryption_iv = b'0' * 16
        ase._client = mock.MagicMock()
        ase._client.primary_endpoint = 'ep'
        ase._name = 'name'
        ase._vio = None
        key = ops.Downloader.create_unique_transfer_operation_id(ase)
        patched_gfr.return_value = b'0' * ase._size
        lp = pathlib.Path(str(tmpdir.join('d')))
        dd = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
        dd.next_offsets()
        dd.write_unchecked_hmac_data = mock.MagicMock()
        dd.perform_chunked_integrity_check = mock.MagicMock()
        dd.mark_unchecked_chunk_decrypted = mock.MagicMock()
        patched_acdd.return_value = b'0' * 16
        d._dd_map[str(lp)] = mock.MagicMock()
        d._transfer_cc[dd.entity.path] = 0
        d._transfer_set.add(key)
        d._transfer_queue = mock.MagicMock()
        d._transfer_queue.get.side_effect = [dd, dd]
        patched_tc.side_effect = [False, True]
        d._worker_thread_transfer()
        assert len(d._disk_set) == 1
        a, b, c = d._disk_queue.get()
        d._process_data(a, b, c)
        assert patched_acdd.call_count == 1
        assert dd.write_unchecked_hmac_data.call_count == 1
        assert dd.perform_chunked_integrity_check.call_count == 1
def test_finalize_integrity_and_file(tmpdir):
    # already finalized
    lp = pathlib.Path(str(tmpdir.join('af')))
    opts = mock.MagicMock()
    opts.check_file_md5 = False
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 32

    data = b'0' * ase._size

    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    d._allocate_disk_space()
    d._finalized = True
    d.finalize_integrity()
    d.finalize_file()

    assert d.final_path.exists()
    assert d.final_path.stat().st_size == ase._size
    d.final_path.unlink()

    # hmac check success
    lp = pathlib.Path(str(tmpdir.join('a')))
    opts = mock.MagicMock()
    opts.check_file_md5 = False
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 32
    ase._encryption = mock.MagicMock()
    ase._encryption.symmetric_key = b'123'
    signkey = os.urandom(32)
    ase._encryption.initialize_hmac = mock.MagicMock()
    ase._encryption.initialize_hmac.return_value = hmac.new(
        signkey, digestmod=hashlib.sha256)

    data = b'0' * (ase._size - 16)
    _hmac = hmac.new(signkey, digestmod=hashlib.sha256)
    _hmac.update(data)
    ase._encryption.encryption_authentication.\
        message_authentication_code = util.base64_encode_as_string(
            _hmac.digest())

    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    d._allocate_disk_space()
    d.hmac.update(data)
    d.finalize_integrity()
    d.finalize_file()

    assert d.final_path.exists()
    assert d.final_path.stat().st_size == len(data)

    # md5 check success
    lp = pathlib.Path(str(tmpdir.join('b')))
    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 32

    data = b'0' * ase._size
    md5 = util.new_md5_hasher()
    md5.update(data)
    ase._md5 = util.base64_encode_as_string(md5.digest())

    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    d._allocate_disk_space()
    d.md5.update(data)
    d.finalize_integrity()
    d.finalize_file()

    assert d.final_path.exists()
    assert d.final_path.stat().st_size == len(data)

    # no check
    lp = pathlib.Path(str(tmpdir.join('c')))
    opts = mock.MagicMock()
    opts.check_file_md5 = False
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 32

    data = b'0' * ase._size

    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    d._allocate_disk_space()
    d.finalize_integrity()
    d.finalize_file()

    assert d.final_path.exists()
    assert d.final_path.stat().st_size == len(data)

    # md5 mismatch
    lp = pathlib.Path(str(tmpdir.join('d')))
    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 32

    data = b'0' * ase._size
    ase._md5 = 'oops'

    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    d._allocate_disk_space()
    d.md5.update(data)
    d.finalize_integrity()
    d.finalize_file()

    assert not d.final_path.exists()
def test_perform_chunked_integrity_check(tmpdir):
    lp = pathlib.Path(str(tmpdir.join('a')))

    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 32
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)

    offsets, _ = d.next_offsets()
    data = b'0' * opts.chunk_size_bytes
    d.write_unchecked_data(offsets, data)
    d.perform_chunked_integrity_check()

    assert d._next_integrity_chunk == 1
    assert 0 not in d._unchecked_chunks
    assert len(d._unchecked_chunks) == 0

    opts = mock.MagicMock()
    opts.check_file_md5 = False
    opts.chunk_size_bytes = 16
    ase = azmodels.StorageEntity('cont')
    ase._size = 32
    ase._encryption = mock.MagicMock()
    ase._encryption.symmetric_key = b'123'
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)

    data = b'0' * opts.chunk_size_bytes
    offsets, _ = d.next_offsets()
    d.write_unchecked_hmac_data(offsets, data)
    ucc = d._unchecked_chunks[offsets.chunk_num]
    offsets1, _ = d.next_offsets()
    d.write_unchecked_hmac_data(offsets1, data)
    ucc1 = d._unchecked_chunks[offsets1.chunk_num]
    ucc['decrypted'] = True
    ucc1['decrypted'] = True
    d.perform_chunked_integrity_check()

    assert ucc['ucc'].file_path != d.final_path
    assert ucc1['ucc'].file_path != d.final_path
    assert d._next_integrity_chunk == 2
    assert 0 not in d._unchecked_chunks
    assert 1 not in d._unchecked_chunks
    assert len(d._unchecked_chunks) == 0

    # check integrity with resume
    resumefile = pathlib.Path(str(tmpdir.join('resume')))
    fp = pathlib.Path(str(tmpdir.join('fp')))

    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 16

    data = b'0' * opts.chunk_size_bytes
    md5 = util.new_md5_hasher()
    md5.update(data)

    ase = azmodels.StorageEntity('cont')
    ase._size = 32
    ase._name = 'blob'
    ase._client = mock.MagicMock()
    ase._md5 = md5.hexdigest()

    rmgr = rops.DownloadResumeManager(resumefile)
    d = models.Descriptor(fp, ase, opts, mock.MagicMock(), rmgr)

    offsets, _ = d.next_offsets()
    d.write_unchecked_data(offsets, data)
    d.perform_chunked_integrity_check()
    assert d._next_integrity_chunk == 1
    assert len(d._unchecked_chunks) == 0
    dr = rmgr.get_record(ase)
    assert dr.next_integrity_chunk == 1
    assert dr.md5hexdigest == md5.hexdigest()
def test_downloaddescriptor_next_offsets(tmpdir):
    lp = pathlib.Path(str(tmpdir.join('a')))

    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 256
    ase = azmodels.StorageEntity('cont')
    ase._size = 128
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)

    offsets, resume_bytes = d.next_offsets()
    assert resume_bytes is None
    assert d._total_chunks == 1
    assert offsets.chunk_num == 0
    assert offsets.fd_start == 0
    assert offsets.num_bytes == 128
    assert offsets.range_start == 0
    assert offsets.range_end == 127
    assert not offsets.unpad
    assert d.next_offsets() == (None, None)

    ase._size = 0
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    assert d._total_chunks == 0
    assert d.next_offsets() == (None, None)

    ase._size = 1
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    offsets, resume_bytes = d.next_offsets()
    assert resume_bytes is None
    assert d._total_chunks == 1
    assert offsets.chunk_num == 0
    assert offsets.fd_start == 0
    assert offsets.num_bytes == 1
    assert offsets.range_start == 0
    assert offsets.range_end == 0
    assert not offsets.unpad
    assert d.next_offsets() == (None, None)

    ase._size = 256
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    offsets, resume_bytes = d.next_offsets()
    assert resume_bytes is None
    assert d._total_chunks == 1
    assert offsets.chunk_num == 0
    assert offsets.fd_start == 0
    assert offsets.num_bytes == 256
    assert offsets.range_start == 0
    assert offsets.range_end == 255
    assert not offsets.unpad
    assert d.next_offsets() == (None, None)

    ase._size = 256 + 16
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    offsets, resume_bytes = d.next_offsets()
    assert resume_bytes is None
    assert d._total_chunks == 2
    assert offsets.chunk_num == 0
    assert offsets.fd_start == 0
    assert offsets.num_bytes == 256
    assert offsets.range_start == 0
    assert offsets.range_end == 255
    assert not offsets.unpad
    offsets, resume_bytes = d.next_offsets()
    assert resume_bytes is None
    assert offsets.chunk_num == 1
    assert offsets.fd_start == 256
    assert offsets.num_bytes == 16
    assert offsets.range_start == 256
    assert offsets.range_end == 256 + 15
    assert not offsets.unpad
    assert d.next_offsets() == (None, None)

    ase._encryption = mock.MagicMock()
    ase._encryption.symmetric_key = b'123'
    ase._size = 128
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    offsets, resume_bytes = d.next_offsets()
    assert resume_bytes is None
    assert d._total_chunks == 1
    assert offsets.chunk_num == 0
    assert offsets.fd_start == 0
    assert offsets.num_bytes == 128
    assert offsets.range_start == 0
    assert offsets.range_end == 127
    assert offsets.unpad
    assert d.next_offsets() == (None, None)

    ase._size = 256
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    offsets, resume_bytes = d.next_offsets()
    assert resume_bytes is None
    assert d._total_chunks == 1
    assert offsets.chunk_num == 0
    assert offsets.fd_start == 0
    assert offsets.num_bytes == 256
    assert offsets.range_start == 0
    assert offsets.range_end == 255
    assert offsets.unpad
    assert d.next_offsets() == (None, None)

    ase._size = 256 + 32  # 16 bytes over + padding
    d = models.Descriptor(lp, ase, opts, mock.MagicMock(), None)
    offsets, resume_bytes = d.next_offsets()
    assert resume_bytes is None
    assert d._total_chunks == 2
    assert offsets.chunk_num == 0
    assert offsets.fd_start == 0
    assert offsets.num_bytes == 256
    assert offsets.range_start == 0
    assert offsets.range_end == 255
    assert not offsets.unpad
    offsets, resume_bytes = d.next_offsets()
    assert resume_bytes is None
    assert offsets.chunk_num == 1
    assert offsets.fd_start == 256
    assert offsets.num_bytes == 32
    assert offsets.range_start == 256 - 16
    assert offsets.range_end == 256 + 31
    assert offsets.unpad
    assert d.next_offsets() == (None, None)
def test_downloaddescriptor_resume(tmpdir):
    resumefile = pathlib.Path(str(tmpdir.join('resume')))
    fp = pathlib.Path(str(tmpdir.join('fp')))

    opts = mock.MagicMock()
    opts.check_file_md5 = True
    opts.chunk_size_bytes = 256
    ase = azmodels.StorageEntity('cont')
    ase._size = 128
    ase._name = 'blob'
    ase._client = mock.MagicMock()

    # test no record
    rmgr = rops.DownloadResumeManager(resumefile)
    d = models.Descriptor(fp, ase, opts, mock.MagicMock(), rmgr)
    rb = d._resume()
    assert rb is None

    # test length mismatch
    rmgr.add_or_update_record(str(fp), ase, 0, 0, False, None)
    ase._size = 127
    rb = d._resume()
    assert rb is None
    ase._size = 128

    # test nothing to resume
    rmgr.delete()
    rmgr = rops.DownloadResumeManager(resumefile)

    rmgr.add_or_update_record(str(fp), ase, 0, 0, False, None)
    d = models.Descriptor(fp, ase, opts, mock.MagicMock(), rmgr)
    rb = d._resume()
    assert rb is None

    # test completion
    rmgr.delete()
    rmgr = rops.DownloadResumeManager(resumefile)

    rmgr.add_or_update_record(str(fp), ase, 32, 1, True, None)
    d = models.Descriptor(fp, ase, opts, mock.MagicMock(), rmgr)
    fp.touch()
    rb = d._resume()
    assert rb == ase._size

    # test encrypted no resume
    fp.unlink()
    rmgr.delete()
    rmgr = rops.DownloadResumeManager(resumefile)

    ase._encryption = mock.MagicMock()
    ase._encryption.symmetric_key = b'123'
    rmgr.add_or_update_record(str(fp), ase, 32, 1, False, None)
    d = models.Descriptor(fp, ase, opts, mock.MagicMock(), rmgr)
    rb = d._resume()
    assert rb is None

    # test up to chunk
    rmgr.delete()
    rmgr = rops.DownloadResumeManager(resumefile)
    ase = azmodels.StorageEntity('cont')
    ase._size = 128
    ase._name = 'blob'
    ase._client = mock.MagicMock()

    rmgr.add_or_update_record(str(fp), ase, 32, 1, False, None)
    d = models.Descriptor(fp, ase, opts, mock.MagicMock(), rmgr)
    rb = d._resume()
    assert rb == 32

    # ensure hmac not populated
    rmgr.delete()
    rmgr = rops.DownloadResumeManager(resumefile)
    ase = azmodels.StorageEntity('cont')
    ase._size = 128
    ase._name = 'blob'
    ase._client = mock.MagicMock()
    fp.touch()

    rmgr.add_or_update_record(str(fp), ase, 32, 1, False, None)
    d = models.Descriptor(fp, ase, opts, mock.MagicMock(), rmgr)
    d.hmac = True
    with pytest.raises(RuntimeError):
        d._resume()

    # md5 hash check
    rmgr.delete()
    rmgr = rops.DownloadResumeManager(resumefile)

    data = os.urandom(32)
    with fp.open('wb') as f:
        f.write(data)
    md5 = util.new_md5_hasher()
    md5.update(data)

    rmgr.add_or_update_record(str(fp), ase, 32, 1, False, md5.hexdigest())
    d = models.Descriptor(fp, ase, opts, mock.MagicMock(), rmgr)
    rb = d._resume()
    assert rb == 32

    # md5 hash mismatch
    rmgr.delete()
    rmgr = rops.DownloadResumeManager(resumefile)
    rmgr.add_or_update_record(str(fp), ase, 32, 1, False, 'abc')
    ase._md5 = 'abc'
    d = models.Descriptor(fp, ase, opts, mock.MagicMock(), rmgr)
    rb = d._resume()
    assert rb is None

    # md5 hash check as page file
    rmgr.delete()
    rmgr = rops.DownloadResumeManager(resumefile)
    ase = azmodels.StorageEntity('cont')
    ase._size = 128
    ase._name = 'blob'
    ase._client = mock.MagicMock()
    ase._mode = azmodels.StorageModes.Page

    rmgr.add_or_update_record(str(fp), ase, 32, 1, False, md5.hexdigest())
    d = models.Descriptor(fp, ase, opts, mock.MagicMock(), rmgr)
    rb = d._resume()
    assert rb == 32