|
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
|
@@ -819,6 +824,53 @@ def test_startup_walk_skip_compression_if_already_compressed(
|
819 | 824 | # uncompressed timeline files are not added to deletion queue, they are immediately unlinked
|
820 | 825 | assert self.pghoard.wal_file_deletion_queue.qsize() == 0
|
821 | 826 |
|
| 827 | + @patch("pghoard.compressor.wal.verify_wal", Mock()) |
| 828 | + @patch.object(PGReceiveXLog, "run", Mock()) |
| 829 | + @patch.object(TransferAgent, "get_object_storage") |
| 830 | + def test_graceful_shutdown( |
| 831 | + self, |
| 832 | + mocked_get_object_storage: MagicMock, |
| 833 | + ) -> None: |
| 834 | + compressed_wal_path, _ = self.pghoard.create_backup_site_paths(self.test_site) |
| 835 | + uncompressed_wal_path = compressed_wal_path + "_incoming" |
| 836 | + |
| 837 | + file_name = "000000010000000000000008" |
| 838 | + uncompressed_file_path = os.path.join(uncompressed_wal_path, file_name) |
| 839 | + with open(uncompressed_file_path, "wb") as fp: |
| 840 | + fp.write(b"foo") |
| 841 | + |
| 842 | + self.pghoard.compression_queue.put( |
| 843 | + CompressionEvent( |
| 844 | + file_type=FileType.Wal, |
| 845 | + file_path=FileTypePrefixes[FileType.Wal] / file_name, |
| 846 | + delete_file_after_compression=True, |
| 847 | + backup_site_name=self.test_site, |
| 848 | + source_data=Path(uncompressed_file_path), |
| 849 | + callback_queue=None, |
| 850 | + metadata={} |
| 851 | + ) |
| 852 | + ) |
| 853 | + |
| 854 | + # run compressors, transfer_agents and wal_file_deleter |
| 855 | + for thread in [*self.pghoard.compressors, *self.pghoard.transfer_agents, self.pghoard.wal_file_deleter]: |
| 856 | + thread.start() |
| 857 | + |
| 858 | + self.pghoard.graceful_shutdown() |
| 859 | + |
| 860 | + assert self.pghoard.compression_queue.qsize() == 0 |
| 861 | + assert self.pghoard.transfer_queue.qsize() == 0 |
| 862 | + assert self.pghoard.wal_file_deletion_queue.qsize() == 0 |
| 863 | + |
| 864 | + # called once for uploading renamed partial file |
| 865 | + assert mocked_get_object_storage.call_count == 1 |
| 866 | + |
| 867 | + # uncompressed file should still exist since WALDeletionThread always keeps last file |
| 868 | + assert os.path.exists(uncompressed_file_path) |
| 869 | + |
| 870 | + # verify compressors, transfer_agents and wal_file_deleter are not running |
| 871 | + for thread in [*self.pghoard.compressors, *self.pghoard.transfer_agents, self.pghoard.wal_file_deleter]: |
| 872 | + assert thread.is_alive() is False |
| 873 | + |
822 | 874 |
|
823 | 875 | class TestPGHoardWithPG:
|
824 | 876 | def test_auth_alert_files(self, db, pghoard):
|
|
0 commit comments