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/__init__.py +0 -0
- pg_probackup2/app.py +762 -0
- pg_probackup2/gdb.py +346 -0
- pg_probackup2/init_helpers.py +207 -0
- pg_probackup2/storage/__init__.py +0 -0
- pg_probackup2/storage/fs_backup.py +101 -0
- pg_probackup2/storage/s3_backup.py +134 -0
- testgres_pg_probackup2-0.0.1.dist-info/METADATA +15 -0
- testgres_pg_probackup2-0.0.1.dist-info/RECORD +11 -0
- testgres_pg_probackup2-0.0.1.dist-info/WHEEL +5 -0
- testgres_pg_probackup2-0.0.1.dist-info/top_level.txt +1 -0
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
|