testgres-pg-probackup2 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pg_probackup2/gdb.py ADDED
@@ -0,0 +1,346 @@
1
+ import functools
2
+ import os
3
+ import re
4
+ import subprocess
5
+ import sys
6
+ import unittest
7
+ from time import sleep
8
+
9
+
10
+ class GdbException(Exception):
11
+ def __init__(self, message="False"):
12
+ self.message = message
13
+
14
+ def __str__(self):
15
+ return '\n ERROR: {0}\n'.format(repr(self.message))
16
+
17
+
18
+ class GDBobj:
19
+ _gdb_enabled = False
20
+ _gdb_ok = False
21
+ _gdb_ptrace_ok = False
22
+
23
+ def __init__(self, cmd, env, attach=False):
24
+ self.verbose = env.verbose
25
+ self.output = ''
26
+ self._did_quit = False
27
+ self.has_breakpoint = False
28
+
29
+ # Check gdb flag is set up
30
+ if not hasattr(env, "_gdb_decorated") or not env._gdb_decorated:
31
+ raise GdbException("Test should be decorated with @needs_gdb")
32
+ if not self._gdb_enabled:
33
+ raise GdbException("No `PGPROBACKUP_GDB=on` is set.")
34
+ if not self._gdb_ok:
35
+ if not self._gdb_ptrace_ok:
36
+ raise GdbException("set /proc/sys/kernel/yama/ptrace_scope to 0"
37
+ " to run GDB tests")
38
+ raise GdbException("No gdb usage possible.")
39
+
40
+ # Check gdb presense
41
+ try:
42
+ gdb_version, _ = subprocess.Popen(
43
+ ['gdb', '--version'],
44
+ stdout=subprocess.PIPE
45
+ ).communicate()
46
+ except OSError:
47
+ raise GdbException("Couldn't find gdb on the path")
48
+
49
+ self.base_cmd = [
50
+ 'gdb',
51
+ '--interpreter',
52
+ 'mi2',
53
+ ]
54
+
55
+ if attach:
56
+ self.cmd = self.base_cmd + ['--pid'] + cmd
57
+ else:
58
+ self.cmd = self.base_cmd + ['--args'] + cmd
59
+
60
+ # Get version
61
+ gdb_version_number = re.search(
62
+ br"^GNU gdb [^\d]*(\d+)\.(\d)",
63
+ gdb_version)
64
+ self.major_version = int(gdb_version_number.group(1))
65
+ self.minor_version = int(gdb_version_number.group(2))
66
+
67
+ if self.verbose:
68
+ print([' '.join(map(str, self.cmd))])
69
+
70
+ self.proc = subprocess.Popen(
71
+ self.cmd,
72
+ stdin=subprocess.PIPE,
73
+ stdout=subprocess.PIPE,
74
+ stderr=subprocess.STDOUT,
75
+ bufsize=0,
76
+ text=True,
77
+ errors='replace',
78
+ )
79
+ self.gdb_pid = self.proc.pid
80
+
81
+ while True:
82
+ line = self.get_line()
83
+
84
+ if 'No such process' in line:
85
+ raise GdbException(line)
86
+
87
+ if not line.startswith('(gdb)'):
88
+ pass
89
+ else:
90
+ break
91
+
92
+ def __del__(self):
93
+ if not self._did_quit and hasattr(self, "proc"):
94
+ try:
95
+ self.quit()
96
+ except subprocess.TimeoutExpired:
97
+ self.kill()
98
+
99
+ def get_line(self):
100
+ line = self.proc.stdout.readline()
101
+ self.output += line
102
+ return line
103
+
104
+ def kill(self):
105
+ self._did_quit = True
106
+ self.proc.kill()
107
+ self.proc.wait(3)
108
+ self.proc.stdin.close()
109
+ self.proc.stdout.close()
110
+
111
+ def set_breakpoint(self, location):
112
+
113
+ result = self._execute('break ' + location)
114
+ self.has_breakpoint = True
115
+ for line in result:
116
+ if line.startswith('~"Breakpoint'):
117
+ return
118
+
119
+ elif line.startswith('=breakpoint-created'):
120
+ return
121
+
122
+ elif line.startswith('^error'): # or line.startswith('(gdb)'):
123
+ break
124
+
125
+ elif line.startswith('&"break'):
126
+ pass
127
+
128
+ elif line.startswith('&"Function'):
129
+ raise GdbException(line)
130
+
131
+ elif line.startswith('&"No line'):
132
+ raise GdbException(line)
133
+
134
+ elif line.startswith('~"Make breakpoint pending on future shared'):
135
+ raise GdbException(line)
136
+
137
+ raise GdbException(
138
+ 'Failed to set breakpoint.\n Output:\n {0}'.format(result)
139
+ )
140
+
141
+ def remove_all_breakpoints(self):
142
+ if not self.has_breakpoint:
143
+ return
144
+
145
+ result = self._execute('delete')
146
+ self.has_breakpoint = False
147
+ for line in result:
148
+
149
+ if line.startswith('^done'):
150
+ return
151
+
152
+ raise GdbException(
153
+ 'Failed to remove breakpoints.\n Output:\n {0}'.format(result)
154
+ )
155
+
156
+ def run_until_break(self):
157
+ result = self._execute('run', False)
158
+ for line in result:
159
+ if line.startswith('*stopped,reason="breakpoint-hit"'):
160
+ return
161
+ raise GdbException(
162
+ 'Failed to run until breakpoint.\n'
163
+ )
164
+
165
+ def continue_execution_until_running(self):
166
+ result = self._execute('continue')
167
+
168
+ for line in result:
169
+ if line.startswith('*running') or line.startswith('^running'):
170
+ return
171
+ if line.startswith('*stopped,reason="breakpoint-hit"'):
172
+ continue
173
+ if line.startswith('*stopped,reason="exited-normally"'):
174
+ continue
175
+
176
+ raise GdbException(
177
+ 'Failed to continue execution until running.\n'
178
+ )
179
+
180
+ def signal(self, sig):
181
+ if 'KILL' in sig:
182
+ self.remove_all_breakpoints()
183
+ self._execute(f'signal {sig}')
184
+
185
+ def continue_execution_until_exit(self):
186
+ self.remove_all_breakpoints()
187
+ result = self._execute('continue', False)
188
+
189
+ for line in result:
190
+ if line.startswith('*running'):
191
+ continue
192
+ if line.startswith('*stopped,reason="breakpoint-hit"'):
193
+ continue
194
+ if line.startswith('*stopped,reason="exited') or line == '*stopped\n':
195
+ self.quit()
196
+ return
197
+
198
+ raise GdbException(
199
+ 'Failed to continue execution until exit.\n'
200
+ )
201
+
202
+ def continue_execution_until_error(self):
203
+ self.remove_all_breakpoints()
204
+ result = self._execute('continue', False)
205
+
206
+ for line in result:
207
+ if line.startswith('^error'):
208
+ return
209
+ if line.startswith('*stopped,reason="exited'):
210
+ return
211
+ if line.startswith(
212
+ '*stopped,reason="signal-received",signal-name="SIGABRT"'):
213
+ return
214
+
215
+ raise GdbException(
216
+ 'Failed to continue execution until error.\n')
217
+
218
+ def continue_execution_until_break(self, ignore_count=0):
219
+ if ignore_count > 0:
220
+ result = self._execute(
221
+ 'continue ' + str(ignore_count),
222
+ False
223
+ )
224
+ else:
225
+ result = self._execute('continue', False)
226
+
227
+ for line in result:
228
+ if line.startswith('*stopped,reason="breakpoint-hit"'):
229
+ return
230
+ if line.startswith('*stopped,reason="exited-normally"'):
231
+ break
232
+
233
+ raise GdbException(
234
+ 'Failed to continue execution until break.\n')
235
+
236
+ def show_backtrace(self):
237
+ return self._execute("backtrace", running=False)
238
+
239
+ def stopped_in_breakpoint(self):
240
+ while True:
241
+ line = self.get_line()
242
+ if self.verbose:
243
+ print(line)
244
+ if line.startswith('*stopped,reason="breakpoint-hit"'):
245
+ return True
246
+
247
+ def detach(self):
248
+ if not self._did_quit:
249
+ self._execute('detach')
250
+
251
+ def quit(self):
252
+ if not self._did_quit:
253
+ self._did_quit = True
254
+ self.proc.terminate()
255
+ self.proc.wait(3)
256
+ self.proc.stdin.close()
257
+ self.proc.stdout.close()
258
+
259
+ # use for breakpoint, run, continue
260
+ def _execute(self, cmd, running=True):
261
+ output = []
262
+ self.proc.stdin.flush()
263
+ self.proc.stdin.write(cmd + '\n')
264
+ self.proc.stdin.flush()
265
+ sleep(1)
266
+
267
+ # look for command we just send
268
+ while True:
269
+ line = self.get_line()
270
+ if self.verbose:
271
+ print(repr(line))
272
+
273
+ if cmd not in line:
274
+ continue
275
+ else:
276
+ break
277
+
278
+ while True:
279
+ line = self.get_line()
280
+ output += [line]
281
+ if self.verbose:
282
+ print(repr(line))
283
+ if line.startswith('^done') or line.startswith('*stopped'):
284
+ break
285
+ if line.startswith('^error'):
286
+ break
287
+ if running and (line.startswith('*running') or line.startswith('^running')):
288
+ # if running and line.startswith('*running'):
289
+ break
290
+ return output
291
+
292
+
293
+ def _set_gdb(self):
294
+ test_env = os.environ.copy()
295
+ self._gdb_enabled = test_env.get('PGPROBACKUP_GDB') == 'ON'
296
+ self._gdb_ok = self._gdb_enabled
297
+ if not self._gdb_enabled or sys.platform != 'linux':
298
+ return
299
+ try:
300
+ with open('/proc/sys/kernel/yama/ptrace_scope') as f:
301
+ ptrace = f.read()
302
+ except FileNotFoundError:
303
+ self._gdb_ptrace_ok = True
304
+ return
305
+ self._gdb_ptrace_ok = int(ptrace) == 0
306
+ self._gdb_ok = self._gdb_ok and self._gdb_ptrace_ok
307
+
308
+
309
+ def _check_gdb_flag_or_skip_test():
310
+ if not GDBobj._gdb_enabled:
311
+ return ("skip",
312
+ "Specify PGPROBACKUP_GDB and build without "
313
+ "optimizations for run this test"
314
+ )
315
+ if GDBobj._gdb_ok:
316
+ return None
317
+ if not GDBobj._gdb_ptrace_ok:
318
+ return ("fail", "set /proc/sys/kernel/yama/ptrace_scope to 0"
319
+ " to run GDB tests")
320
+ else:
321
+ return ("fail", "use of gdb is not possible")
322
+
323
+
324
+ def needs_gdb(func):
325
+ check = _check_gdb_flag_or_skip_test()
326
+ if not check:
327
+ @functools.wraps(func)
328
+ def ok_wrapped(self):
329
+ self._gdb_decorated = True
330
+ func(self)
331
+
332
+ return ok_wrapped
333
+ reason = check[1]
334
+ if check[0] == "skip":
335
+ return unittest.skip(reason)(func)
336
+ elif check[0] == "fail":
337
+ @functools.wraps(func)
338
+ def fail_wrapper(self):
339
+ self.fail(reason)
340
+
341
+ return fail_wrapper
342
+ else:
343
+ raise "Wrong action {0}".format(check)
344
+
345
+
346
+ _set_gdb(GDBobj)
@@ -0,0 +1,207 @@
1
+ from functools import reduce
2
+ import getpass
3
+ import os
4
+ import re
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ import testgres
9
+
10
+ try:
11
+ import lz4.frame # noqa: F401
12
+
13
+ HAVE_LZ4 = True
14
+ except ImportError as e:
15
+ HAVE_LZ4 = False
16
+ LZ4_error = e
17
+
18
+ try:
19
+ import zstd # noqa: F401
20
+
21
+ HAVE_ZSTD = True
22
+ except ImportError as e:
23
+ HAVE_ZSTD = False
24
+ ZSTD_error = e
25
+
26
+ delete_logs = os.getenv('KEEP_LOGS') not in ['1', 'y', 'Y']
27
+
28
+ try:
29
+ testgres.configure_testgres(
30
+ cache_initdb=False,
31
+ cached_initdb_dir=False,
32
+ node_cleanup_full=delete_logs)
33
+ except Exception as e:
34
+ print("Can't configure testgres: {0}".format(e))
35
+
36
+
37
+ class Init(object):
38
+ def __init__(self):
39
+ if '-v' in sys.argv or '--verbose' in sys.argv:
40
+ self.verbose = True
41
+ else:
42
+ self.verbose = False
43
+
44
+ self._pg_config = testgres.get_pg_config()
45
+ self.is_enterprise = self._pg_config.get('PGPRO_EDITION', None) == 'enterprise'
46
+ self.is_shardman = self._pg_config.get('PGPRO_EDITION', None) == 'shardman'
47
+ self.is_pgpro = 'PGPRO_EDITION' in self._pg_config
48
+ self.is_nls_enabled = 'enable-nls' in self._pg_config['CONFIGURE']
49
+ self.is_lz4_enabled = '-llz4' in self._pg_config['LIBS']
50
+ version = self._pg_config['VERSION'].rstrip('develalphabetapre')
51
+ parts = [*version.split(' ')[1].split('.'), '0', '0'][:3]
52
+ parts[0] = re.match(r'\d+', parts[0]).group()
53
+ self.pg_config_version = reduce(lambda v, x: v * 100 + int(x), parts, 0)
54
+
55
+ test_env = os.environ.copy()
56
+ envs_list = [
57
+ 'LANGUAGE',
58
+ 'LC_ALL',
59
+ 'PGCONNECT_TIMEOUT',
60
+ 'PGDATA',
61
+ 'PGDATABASE',
62
+ 'PGHOSTADDR',
63
+ 'PGREQUIRESSL',
64
+ 'PGSERVICE',
65
+ 'PGSSLMODE',
66
+ 'PGUSER',
67
+ 'PGPORT',
68
+ 'PGHOST'
69
+ ]
70
+
71
+ for e in envs_list:
72
+ test_env.pop(e, None)
73
+
74
+ test_env['LC_MESSAGES'] = 'C'
75
+ test_env['LC_TIME'] = 'C'
76
+ self._test_env = test_env
77
+
78
+ # Get the directory from which the script was executed
79
+ self.source_path = os.getcwd()
80
+ tmp_path = test_env.get('PGPROBACKUP_TMP_DIR')
81
+ if tmp_path and os.path.isabs(tmp_path):
82
+ self.tmp_path = tmp_path
83
+ else:
84
+ self.tmp_path = os.path.abspath(
85
+ os.path.join(self.source_path, tmp_path or os.path.join('tests', 'tmp_dirs'))
86
+ )
87
+
88
+ os.makedirs(self.tmp_path, exist_ok=True)
89
+
90
+ self.username = getpass.getuser()
91
+
92
+ self.probackup_path = None
93
+ if 'PGPROBACKUPBIN' in test_env:
94
+ if shutil.which(test_env["PGPROBACKUPBIN"]):
95
+ self.probackup_path = test_env["PGPROBACKUPBIN"]
96
+ else:
97
+ if self.verbose:
98
+ print('PGPROBACKUPBIN is not an executable file')
99
+
100
+ if not self.probackup_path:
101
+ probackup_path_tmp = os.path.join(
102
+ testgres.get_pg_config()['BINDIR'], 'pg_probackup')
103
+
104
+ if os.path.isfile(probackup_path_tmp):
105
+ if not os.access(probackup_path_tmp, os.X_OK):
106
+ print('{0} is not an executable file'.format(
107
+ probackup_path_tmp))
108
+ else:
109
+ self.probackup_path = probackup_path_tmp
110
+
111
+ if not self.probackup_path:
112
+ probackup_path_tmp = self.source_path
113
+
114
+ if os.path.isfile(probackup_path_tmp):
115
+ if not os.access(probackup_path_tmp, os.X_OK):
116
+ print('{0} is not an executable file'.format(
117
+ probackup_path_tmp))
118
+ else:
119
+ self.probackup_path = probackup_path_tmp
120
+
121
+ if not self.probackup_path:
122
+ print('pg_probackup binary is not found')
123
+ exit(1)
124
+
125
+ if os.name == 'posix':
126
+ self.EXTERNAL_DIRECTORY_DELIMITER = ':'
127
+ os.environ['PATH'] = os.path.dirname(
128
+ self.probackup_path) + ':' + os.environ['PATH']
129
+
130
+ elif os.name == 'nt':
131
+ self.EXTERNAL_DIRECTORY_DELIMITER = ';'
132
+ os.environ['PATH'] = os.path.dirname(
133
+ self.probackup_path) + ';' + os.environ['PATH']
134
+
135
+ self.probackup_old_path = None
136
+ if 'PGPROBACKUPBIN_OLD' in test_env:
137
+ if (os.path.isfile(test_env['PGPROBACKUPBIN_OLD']) and os.access(test_env['PGPROBACKUPBIN_OLD'], os.X_OK)):
138
+ self.probackup_old_path = test_env['PGPROBACKUPBIN_OLD']
139
+ else:
140
+ if self.verbose:
141
+ print('PGPROBACKUPBIN_OLD is not an executable file')
142
+
143
+ self.probackup_version = None
144
+ self.old_probackup_version = None
145
+
146
+ probackup_version_output = subprocess.check_output(
147
+ [self.probackup_path, "--version"],
148
+ stderr=subprocess.STDOUT,
149
+ ).decode('utf-8')
150
+ self.probackup_version = re.search(r"\d+\.\d+\.\d+",
151
+ probackup_version_output
152
+ ).group(0)
153
+ compressions = re.search(r"\(compressions: ([^)]*)\)",
154
+ probackup_version_output).group(1)
155
+ self.probackup_compressions = {s.strip() for s in compressions.split(',')}
156
+
157
+ if self.probackup_old_path:
158
+ old_probackup_version_output = subprocess.check_output(
159
+ [self.probackup_old_path, "--version"],
160
+ stderr=subprocess.STDOUT,
161
+ ).decode('utf-8')
162
+ self.old_probackup_version = re.search(r"\d+\.\d+\.\d+",
163
+ old_probackup_version_output
164
+ ).group(0)
165
+
166
+ self.remote = test_env.get('PGPROBACKUP_SSH_REMOTE', None) == 'ON'
167
+ self.ptrack = test_env.get('PG_PROBACKUP_PTRACK', None) == 'ON' and self.pg_config_version >= 110000
168
+
169
+ self.paranoia = test_env.get('PG_PROBACKUP_PARANOIA', None) == 'ON'
170
+ env_compress = test_env.get('ARCHIVE_COMPRESSION', None)
171
+ if env_compress:
172
+ env_compress = env_compress.lower()
173
+ if env_compress in ('on', 'zlib'):
174
+ self.compress_suffix = '.gz'
175
+ self.archive_compress = 'zlib'
176
+ elif env_compress == 'lz4':
177
+ if not HAVE_LZ4:
178
+ raise LZ4_error
179
+ if 'lz4' not in self.probackup_compressions:
180
+ raise Exception("pg_probackup is not compiled with lz4 support")
181
+ self.compress_suffix = '.lz4'
182
+ self.archive_compress = 'lz4'
183
+ elif env_compress == 'zstd':
184
+ if not HAVE_ZSTD:
185
+ raise ZSTD_error
186
+ if 'zstd' not in self.probackup_compressions:
187
+ raise Exception("pg_probackup is not compiled with zstd support")
188
+ self.compress_suffix = '.zst'
189
+ self.archive_compress = 'zstd'
190
+ else:
191
+ self.compress_suffix = ''
192
+ self.archive_compress = False
193
+
194
+ cfs_compress = test_env.get('PG_PROBACKUP_CFS_COMPRESS', None)
195
+ if cfs_compress:
196
+ self.cfs_compress = cfs_compress.lower()
197
+ else:
198
+ self.cfs_compress = self.archive_compress
199
+
200
+ os.environ["PGAPPNAME"] = "pg_probackup"
201
+ self.delete_logs = delete_logs
202
+
203
+ def test_env(self):
204
+ return self._test_env.copy()
205
+
206
+
207
+ init_params = Init()
File without changes
@@ -0,0 +1,101 @@
1
+ """
2
+ Utilities for accessing pg_probackup backup data on file system.
3
+ """
4
+ import os
5
+ import shutil
6
+
7
+ from ..init_helpers import init_params
8
+
9
+
10
+ class TestBackupDir:
11
+
12
+ def list_instance_backups(self, instance):
13
+ raise NotImplementedError()
14
+
15
+ def list_files(self, sub_dir, recursive=False):
16
+ raise NotImplementedError()
17
+
18
+ def list_dirs(self, sub_dir):
19
+ raise NotImplementedError()
20
+
21
+ def read_file(self, sub_path, *, text=True):
22
+ raise NotImplementedError()
23
+
24
+ def write_file(self, sub_path, data, *, text=True):
25
+ raise NotImplementedError()
26
+
27
+ def cleanup(self):
28
+ raise NotImplementedError()
29
+
30
+ def remove_file(self, sub_path):
31
+ raise NotImplementedError()
32
+
33
+ def remove_dir(self, sub_path):
34
+ raise NotImplementedError()
35
+
36
+ def exists(self, sub_path):
37
+ raise NotImplementedError()
38
+
39
+
40
+ class FSTestBackupDir(TestBackupDir):
41
+ is_file_based = True
42
+
43
+ """ Backup directory. Usually created by running pg_probackup init -B <path>"""
44
+
45
+ def __init__(self, *, rel_path, backup):
46
+ self.path = os.path.join(init_params.tmp_path, rel_path, backup)
47
+ self.pb_args = ('-B', self.path)
48
+
49
+ def list_instance_backups(self, instance):
50
+ full_path = os.path.join(self.path, 'backups', instance)
51
+ return sorted((x for x in os.listdir(full_path)
52
+ if os.path.isfile(os.path.join(full_path, x, 'backup.control'))))
53
+
54
+ def list_files(self, sub_dir, recursive=False):
55
+ full_path = os.path.join(self.path, sub_dir)
56
+ if not recursive:
57
+ return [f for f in os.listdir(full_path)
58
+ if os.path.isfile(os.path.join(full_path, f))]
59
+ files = []
60
+ for rootdir, dirs, files_in_dir in os.walk(full_path):
61
+ rootdir = rootdir[len(self.path) + 1:]
62
+ files.extend(os.path.join(rootdir, file) for file in files_in_dir)
63
+ return files
64
+
65
+ def list_dirs(self, sub_dir):
66
+ full_path = os.path.join(self.path, sub_dir)
67
+ return [f for f in os.listdir(full_path)
68
+ if os.path.isdir(os.path.join(full_path, f))]
69
+
70
+ def read_file(self, sub_path, *, text=True):
71
+ full_path = os.path.join(self.path, sub_path)
72
+ with open(full_path, 'r' if text else 'rb') as fin:
73
+ return fin.read()
74
+
75
+ def write_file(self, sub_path, data, *, text=True):
76
+ full_path = os.path.join(self.path, sub_path)
77
+ with open(full_path, 'w' if text else 'wb') as fout:
78
+ fout.write(data)
79
+
80
+ def cleanup(self):
81
+ shutil.rmtree(self.path, ignore_errors=True)
82
+
83
+ def remove_file(self, sub_path):
84
+ os.remove(os.path.join(self.path, sub_path))
85
+
86
+ def remove_dir(self, sub_path):
87
+ full_path = os.path.join(self.path, sub_path)
88
+ shutil.rmtree(full_path, ignore_errors=True)
89
+
90
+ def exists(self, sub_path):
91
+ full_path = os.path.join(self.path, sub_path)
92
+ return os.path.exists(full_path)
93
+
94
+ def __str__(self):
95
+ return self.path
96
+
97
+ def __repr__(self):
98
+ return "FSTestBackupDir" + str(self.path)
99
+
100
+ def __fspath__(self):
101
+ return self.path