|
11 | 11 | import time
|
12 | 12 | from pathlib import Path
|
13 | 13 | from typing import Any, Dict
|
14 |
| -from unittest.mock import Mock, patch |
| 14 | +from unittest.mock import MagicMock, Mock, patch |
15 | 15 |
|
16 | 16 | import pytest
|
17 | 17 |
|
18 | 18 | import pghoard.pghoard as pghoard_module
|
19 |
| -from pghoard.common import (BaseBackupFormat, FileType, create_alert_file, delete_alert_file, write_json_file) |
| 19 | +from pghoard.common import ( |
| 20 | + BaseBackupFormat, FileType, FileTypePrefixes, create_alert_file, delete_alert_file, write_json_file |
| 21 | +) |
| 22 | +from pghoard.compressor import CompressionEvent |
20 | 23 | from pghoard.pghoard import PGHoard
|
21 | 24 | from pghoard.pgutil import create_connection_string
|
| 25 | +from pghoard.receivexlog import PGReceiveXLog |
| 26 | +from pghoard.transfer import TransferAgent |
22 | 27 |
|
23 | 28 | from .base import PGHoardTestCase
|
24 | 29 | from .util import dict_to_tar_file, switch_wal, wait_for_xlog
|
@@ -755,6 +760,61 @@ def test_startup_walk_for_missed_compressed_file_type(self, file_type: FileType,
|
755 | 760 | upload_event = self.pghoard.transfer_queue.get(timeout=1.0)
|
756 | 761 | assert upload_event.file_type == file_type
|
757 | 762 |
|
| 763 | + @patch("pghoard.compressor.wal.verify_wal", Mock()) |
| 764 | + @patch.object(PGReceiveXLog, "run", Mock()) |
| 765 | + @patch.object(TransferAgent, "get_object_storage") |
| 766 | + def test_graceful_shutdown_with_partial_wal_file( |
| 767 | + self, |
| 768 | + mocked_get_object_storage: MagicMock, |
| 769 | + ) -> None: |
| 770 | + compressed_wal_path, _ = self.pghoard.create_backup_site_paths(self.test_site) |
| 771 | + uncompressed_wal_path = compressed_wal_path + "_incoming" |
| 772 | + |
| 773 | + self.config["backup_sites"][self.test_site]["active_backup_mode"] = "pg_receivexlog" |
| 774 | + |
| 775 | + self.pghoard.receivexlog_listener( |
| 776 | + self.test_site, self.config["backup_sites"][self.test_site]["nodes"][0], uncompressed_wal_path |
| 777 | + ) |
| 778 | + |
| 779 | + assert len(self.pghoard.receivexlogs) == 1 |
| 780 | + |
| 781 | + file_name = "000000010000000000000008" |
| 782 | + uncompressed_file_path = os.path.join(uncompressed_wal_path, file_name) |
| 783 | + with open(uncompressed_file_path, "wb") as fp: |
| 784 | + fp.write(b"foo") |
| 785 | + |
| 786 | + self.pghoard.compression_queue.put( |
| 787 | + CompressionEvent( |
| 788 | + file_type=FileType.Wal, |
| 789 | + file_path=FileTypePrefixes[FileType.Wal] / file_name, |
| 790 | + delete_file_after_compression=True, |
| 791 | + backup_site_name=self.test_site, |
| 792 | + source_data=Path(uncompressed_file_path), |
| 793 | + callback_queue=None, |
| 794 | + metadata={} |
| 795 | + ) |
| 796 | + ) |
| 797 | + |
| 798 | + # run compressors, transfer_agents and wal_file_deleter |
| 799 | + for thread in [*self.pghoard.compressors, *self.pghoard.transfer_agents, self.pghoard.wal_file_deleter]: |
| 800 | + thread.start() |
| 801 | + |
| 802 | + self.pghoard.graceful_shutdown() |
| 803 | + |
| 804 | + assert self.pghoard.compression_queue.qsize() == 0 |
| 805 | + assert self.pghoard.transfer_queue.qsize() == 0 |
| 806 | + assert self.pghoard.wal_file_deletion_queue.qsize() == 0 |
| 807 | + |
| 808 | + # called once for uploading renamed partial file |
| 809 | + assert mocked_get_object_storage.call_count == 1 |
| 810 | + |
| 811 | + # uncompressed file should still exist since WALDeletionThread always keeps last file |
| 812 | + assert os.path.exists(uncompressed_file_path) |
| 813 | + |
| 814 | + # verify compressors, transfer_agents and wal_file_deleter are not running |
| 815 | + for thread in [*self.pghoard.compressors, *self.pghoard.transfer_agents, self.pghoard.wal_file_deleter]: |
| 816 | + assert thread.is_alive() is False |
| 817 | + |
758 | 818 |
|
759 | 819 | class TestPGHoardWithPG:
|
760 | 820 | def test_auth_alert_files(self, db, pghoard):
|
|
0 commit comments