testgres-pg-probackup2 0.0.1__tar.gz → 0.0.3__tar.gz

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.
Files changed (16) hide show
  1. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/PKG-INFO +1 -1
  2. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/README.md +6 -14
  3. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/pg_probackup2/app.py +66 -26
  4. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/pg_probackup2/init_helpers.py +19 -9
  5. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/pg_probackup2/storage/fs_backup.py +4 -1
  6. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/setup.py +1 -1
  7. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/testgres_pg_probackup2.egg-info/PKG-INFO +1 -1
  8. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/testgres_pg_probackup2.egg-info/SOURCES.txt +0 -1
  9. testgres_pg_probackup2-0.0.1/pg_probackup2/storage/s3_backup.py +0 -134
  10. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/pg_probackup2/__init__.py +0 -0
  11. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/pg_probackup2/gdb.py +0 -0
  12. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/pg_probackup2/storage/__init__.py +0 -0
  13. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/setup.cfg +0 -0
  14. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/testgres_pg_probackup2.egg-info/dependency_links.txt +0 -0
  15. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/testgres_pg_probackup2.egg-info/requires.txt +0 -0
  16. {testgres_pg_probackup2-0.0.1 → testgres_pg_probackup2-0.0.3}/testgres_pg_probackup2.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: testgres_pg_probackup2
3
- Version: 0.0.1
3
+ Version: 0.0.3
4
4
  Summary: Plugin for testgres that manages pg_probackup2
5
5
  Home-page: https://github.com/postgrespro/testgres
6
6
  Author: Postgres Professional
@@ -16,22 +16,14 @@ The package requires testgres~=1.9.3.
16
16
 
17
17
  ## Usage
18
18
 
19
- ### Environment
19
+ ### Environment variables
20
20
 
21
- > Note: by default testgres runs `initdb`, `pg_ctl`, `psql` provided by `PATH`.
22
-
23
- There are several ways to specify a custom postgres installation:
24
-
25
- * export `PG_CONFIG` environment variable pointing to the `pg_config` executable;
26
- * export `PG_BIN` environment variable pointing to the directory with executable files.
27
-
28
- Example:
29
-
30
- ```bash
31
- export PG_BIN=$HOME/pg/bin
32
- python my_tests.py
33
- ```
21
+ | Variable | Required | Default value | Description |
22
+ | - | - | - | - |
23
+ | PGPROBACKUP_TMP_DIR | No | tests/tmp_dirs | The root of the temporary directory hierarchy where tests store data and logs. Relative paths start from the current working directory. |
24
+ | PG_PROBACKUP_TEST_BACKUP_DIR_PREFIX | No | Temporary test hierarchy | Prefix of the test backup directories. Must be an absolute path. Use this variable to store test backups in a location other than the temporary test hierarchy. |
34
25
 
26
+ See [Testgres](https://github.com/postgrespro/testgres/tree/master#environment) on how to configure a custom Postgres installation using `PG_CONFIG` and `PG_BIN` environment variables.
35
27
 
36
28
  ### Examples
37
29
 
@@ -4,7 +4,6 @@ import json
4
4
  import os
5
5
  import re
6
6
  import subprocess
7
- import sys
8
7
  import threading
9
8
  import time
10
9
  import unittest
@@ -37,6 +36,10 @@ class ProbackupException(Exception):
37
36
  return '\n ERROR: {0}\n CMD: {1}'.format(repr(self.message), self.cmd)
38
37
 
39
38
 
39
+ # Local backup control
40
+ fs_backup_class = FSTestBackupDir
41
+
42
+
40
43
  class ProbackupApp:
41
44
 
42
45
  def __init__(self, test_class: unittest.TestCase,
@@ -53,6 +56,7 @@ class ProbackupApp:
53
56
  self.verbose = init_params.verbose
54
57
  self.archive_compress = init_params.archive_compress
55
58
  self.test_class.output = None
59
+ self.execution_time = None
56
60
 
57
61
  def run(self, command, gdb=False, old_binary=False, return_id=True, env=None,
58
62
  skip_log_directory=False, expect_error=False, use_backup_dir=True):
@@ -110,16 +114,17 @@ class ProbackupApp:
110
114
  cmdline = ['gdbserver'] + ['localhost:' + str(gdb_port)] + cmdline
111
115
  print("pg_probackup gdb suspended, waiting gdb connection on localhost:{0}".format(gdb_port))
112
116
 
117
+ start_time = time.time()
113
118
  self.test_class.output = subprocess.check_output(
114
119
  cmdline,
115
120
  stderr=subprocess.STDOUT,
116
121
  env=env
117
122
  ).decode('utf-8', errors='replace')
123
+ end_time = time.time()
124
+ self.execution_time = end_time - start_time
125
+
118
126
  if command[0] == 'backup' and return_id:
119
- # return backup ID
120
- for line in self.test_class.output.splitlines():
121
- if 'INFO: Backup' and 'completed' in line:
122
- result = line.split()[2]
127
+ result = self.get_backup_id()
123
128
  else:
124
129
  result = self.test_class.output
125
130
  if expect_error is True:
@@ -136,6 +141,19 @@ class ProbackupApp:
136
141
  else:
137
142
  raise ProbackupException(self.test_class.output, self.test_class.cmd)
138
143
 
144
+ def get_backup_id(self):
145
+ if init_params.major_version > 2:
146
+ pattern = re.compile(r"Backup (.*) completed successfully.")
147
+ for line in self.test_class.output.splitlines():
148
+ match = pattern.search(line)
149
+ if match:
150
+ return match.group(1)
151
+ else:
152
+ for line in self.test_class.output.splitlines():
153
+ if 'INFO: Backup' and 'completed' in line:
154
+ return line.split()[2]
155
+ return None
156
+
139
157
  def init(self, options=None, old_binary=False, skip_log_directory=False, expect_error=False, use_backup_dir=True):
140
158
  if options is None:
141
159
  options = []
@@ -146,13 +164,19 @@ class ProbackupApp:
146
164
  use_backup_dir=use_backup_dir
147
165
  )
148
166
 
149
- def add_instance(self, instance, node, old_binary=False, options=None, expect_error=False):
167
+ def add_instance(self, instance, node, old_binary=False, options=None, expect_error=False, datname=False):
150
168
  if options is None:
151
169
  options = []
170
+
171
+ if not datname:
172
+ datname = 'postgres'
173
+
152
174
  cmd = [
153
175
  'add-instance',
154
176
  '--instance={0}'.format(instance),
155
- '-D', node.data_dir
177
+ '-D', node.data_dir,
178
+ '--pgport', '%i' % node.port,
179
+ '--pgdatabase', datname
156
180
  ]
157
181
 
158
182
  # don`t forget to kill old_binary after remote ssh release
@@ -189,15 +213,13 @@ class ProbackupApp:
189
213
 
190
214
  return self.run(cmd + options, old_binary=old_binary, expect_error=expect_error)
191
215
 
192
- def del_instance(self, instance, old_binary=False, expect_error=False):
193
-
194
- return self.run([
195
- 'del-instance',
196
- '--instance={0}'.format(instance),
197
- ],
198
- old_binary=old_binary,
199
- expect_error=expect_error
200
- )
216
+ def del_instance(self, instance, options=None, old_binary=False, expect_error=False):
217
+ if options is None:
218
+ options = []
219
+ cmd = ['del-instance', '--instance={0}'.format(instance)] + options
220
+ return self.run(cmd,
221
+ old_binary=old_binary,
222
+ expect_error=expect_error)
201
223
 
202
224
  def backup_node(
203
225
  self, instance, node, data_dir=False,
@@ -662,7 +684,11 @@ class ProbackupApp:
662
684
  if custom_archive_command is None:
663
685
  archive_command = " ".join([f'"{init_params.probackup_path}"',
664
686
  'archive-push', *self.backup_dir.pb_args])
665
- if os.name == "nt":
687
+ if os.name == 'posix':
688
+ # Dash produces a core dump when it gets a SIGQUIT from its
689
+ # child process so replace the shell with pg_probackup
690
+ archive_command = 'exec ' + archive_command
691
+ elif os.name == "nt":
666
692
  archive_command = archive_command.replace("\\", "\\\\")
667
693
  archive_command += f' --instance={instance}'
668
694
 
@@ -747,16 +773,30 @@ class ProbackupApp:
747
773
 
748
774
  return getattr(module, class_name)
749
775
 
776
+ def archive_push(self, instance, node, wal_file_name, wal_file_path=None, options=None, expect_error=False):
777
+ if options is None:
778
+ options = []
779
+ cmd = [
780
+ 'archive-push',
781
+ '--instance={0}'.format(instance),
782
+ '--wal-file-name={0}'.format(wal_file_name),
783
+ ]
784
+ if wal_file_path is None:
785
+ cmd = cmd + ['--wal-file-path={0}'.format(os.path.join(node.data_dir, 'pg_wal'))]
786
+ else:
787
+ cmd = cmd + ['--wal-file-path={0}'.format(wal_file_path)]
788
+ return self.run(cmd + options, expect_error=expect_error)
750
789
 
751
- # Local or S3 backup
752
- fs_backup_class = FSTestBackupDir
753
- if os.environ.get('PG_PROBACKUP_S3_TEST', os.environ.get('PROBACKUP_S3_TYPE_FULL_TEST')):
754
- root = os.path.realpath(os.path.join(os.path.dirname(__file__), '../..'))
755
- if root not in sys.path:
756
- sys.path.append(root)
757
- from pg_probackup2.storage.s3_backup import S3TestBackupDir
758
-
759
- fs_backup_class = S3TestBackupDir
790
+ def archive_get(self, instance, wal_file_name, wal_file_path, options=None, expect_error=False):
791
+ if options is None:
792
+ options = []
793
+ cmd = [
794
+ 'archive-get',
795
+ '--instance={0}'.format(instance),
796
+ '--wal-file-name={0}'.format(wal_file_name),
797
+ '--wal-file-path={0}'.format(wal_file_path),
798
+ ]
799
+ return self.run(cmd + options, expect_error=expect_error)
760
800
 
761
801
  def build_backup_dir(self, backup='backup'):
762
802
  return fs_backup_class(rel_path=self.rel_path, backup=backup)
@@ -52,6 +52,7 @@ class Init(object):
52
52
  parts[0] = re.match(r'\d+', parts[0]).group()
53
53
  self.pg_config_version = reduce(lambda v, x: v * 100 + int(x), parts, 0)
54
54
 
55
+ os.environ['LANGUAGE'] = 'en' # set default locale language to en. All messages will use this locale
55
56
  test_env = os.environ.copy()
56
57
  envs_list = [
57
58
  'LANGUAGE',
@@ -147,21 +148,24 @@ class Init(object):
147
148
  [self.probackup_path, "--version"],
148
149
  stderr=subprocess.STDOUT,
149
150
  ).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(',')}
151
+ match = re.search(r"\d+\.\d+\.\d+",
152
+ probackup_version_output)
153
+ self.probackup_version = match.group(0) if match else None
154
+ match = re.search(r"\(compressions: ([^)]*)\)", probackup_version_output)
155
+ compressions = match.group(1) if match else None
156
+ if compressions:
157
+ self.probackup_compressions = {s.strip() for s in compressions.split(',')}
158
+ else:
159
+ self.probackup_compressions = []
156
160
 
157
161
  if self.probackup_old_path:
158
162
  old_probackup_version_output = subprocess.check_output(
159
163
  [self.probackup_old_path, "--version"],
160
164
  stderr=subprocess.STDOUT,
161
165
  ).decode('utf-8')
162
- self.old_probackup_version = re.search(r"\d+\.\d+\.\d+",
163
- old_probackup_version_output
164
- ).group(0)
166
+ match = re.search(r"\d+\.\d+\.\d+",
167
+ old_probackup_version_output)
168
+ self.old_probackup_version = match.group(0) if match else None
165
169
 
166
170
  self.remote = test_env.get('PGPROBACKUP_SSH_REMOTE', None) == 'ON'
167
171
  self.ptrack = test_env.get('PG_PROBACKUP_PTRACK', None) == 'ON' and self.pg_config_version >= 110000
@@ -200,6 +204,12 @@ class Init(object):
200
204
  os.environ["PGAPPNAME"] = "pg_probackup"
201
205
  self.delete_logs = delete_logs
202
206
 
207
+ if self.probackup_version.split('.')[0].isdigit():
208
+ self.major_version = int(self.probackup_version.split('.')[0])
209
+ else:
210
+ print('Can\'t process pg_probackup version \"{}\": the major version is expected to be a number'.format(self.probackup_version))
211
+ sys.exit(1)
212
+
203
213
  def test_env(self):
204
214
  return self._test_env.copy()
205
215
 
@@ -43,7 +43,10 @@ class FSTestBackupDir(TestBackupDir):
43
43
  """ Backup directory. Usually created by running pg_probackup init -B <path>"""
44
44
 
45
45
  def __init__(self, *, rel_path, backup):
46
- self.path = os.path.join(init_params.tmp_path, rel_path, backup)
46
+ backup_prefix = os.environ.get('PG_PROBACKUP_TEST_BACKUP_DIR_PREFIX')
47
+ if backup_prefix and not os.path.isabs(backup_prefix):
48
+ raise Exception(f"PG_PROBACKUP_TEST_BACKUP_DIR_PREFIX must be an absolute path, current value: {backup_prefix}")
49
+ self.path = os.path.join(backup_prefix or init_params.tmp_path, rel_path, backup)
47
50
  self.pb_args = ('-B', self.path)
48
51
 
49
52
  def list_instance_backups(self, instance):
@@ -4,7 +4,7 @@ except ImportError:
4
4
  from distutils.core import setup
5
5
 
6
6
  setup(
7
- version='0.0.1',
7
+ version='0.0.3',
8
8
  name='testgres_pg_probackup2',
9
9
  packages=['pg_probackup2', 'pg_probackup2.storage'],
10
10
  description='Plugin for testgres that manages pg_probackup2',
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: testgres-pg-probackup2
3
- Version: 0.0.1
3
+ Version: 0.0.3
4
4
  Summary: Plugin for testgres that manages pg_probackup2
5
5
  Home-page: https://github.com/postgrespro/testgres
6
6
  Author: Postgres Professional
@@ -6,7 +6,6 @@ pg_probackup2/gdb.py
6
6
  pg_probackup2/init_helpers.py
7
7
  pg_probackup2/storage/__init__.py
8
8
  pg_probackup2/storage/fs_backup.py
9
- pg_probackup2/storage/s3_backup.py
10
9
  testgres_pg_probackup2.egg-info/PKG-INFO
11
10
  testgres_pg_probackup2.egg-info/SOURCES.txt
12
11
  testgres_pg_probackup2.egg-info/dependency_links.txt
@@ -1,134 +0,0 @@
1
- import os
2
- import io
3
- import sys
4
-
5
- import minio
6
- from minio import Minio
7
- from minio.deleteobjects import DeleteObject
8
- import urllib3
9
- from .fs_backup import TestBackupDir
10
-
11
- root = os.path.realpath(os.path.join(os.path.dirname(__file__), '../..'))
12
- if root not in sys.path:
13
- sys.path.append(root)
14
-
15
- # Should fail if either of env vars does not exist
16
- host = os.environ['PG_PROBACKUP_S3_HOST']
17
- port = os.environ['PG_PROBACKUP_S3_PORT']
18
- access = os.environ['PG_PROBACKUP_S3_ACCESS_KEY']
19
- secret = os.environ['PG_PROBACKUP_S3_SECRET_ACCESS_KEY']
20
- bucket = os.environ['PG_PROBACKUP_S3_BUCKET_NAME']
21
- path_suffix = os.environ.get("PG_PROBACKUP_TEST_TMP_SUFFIX")
22
- https = os.environ.get("PG_PROBACKUP_S3_HTTPS")
23
-
24
- s3_type = os.environ.get('PG_PROBACKUP_S3_TEST', os.environ.get('PROBACKUP_S3_TYPE_FULL_TEST'))
25
- tmp_path = os.environ.get('PGPROBACKUP_TMP_DIR', default='')
26
-
27
- status_forcelist = [413, # RequestBodyTooLarge
28
- 429, # TooManyRequests
29
- 500, # InternalError
30
- 503, # ServerBusy
31
- ]
32
-
33
-
34
- class S3TestBackupDir(TestBackupDir):
35
- is_file_based = False
36
-
37
- def __init__(self, *, rel_path, backup):
38
- path = "pg_probackup"
39
- if path_suffix:
40
- path += "_" + path_suffix
41
- if tmp_path == '' or os.path.isabs(tmp_path):
42
- self.path = f"{path}{tmp_path}/{rel_path}/{backup}"
43
- else:
44
- self.path = f"{path}/{tmp_path}/{rel_path}/{backup}"
45
-
46
- secure: bool = False
47
- if https in ['ON', 'HTTPS']:
48
- secure = True
49
- self.conn = Minio(host + ":" + port, secure=secure, access_key=access,
50
- secret_key=secret, http_client=urllib3.PoolManager(retries=urllib3.Retry(total=5,
51
- backoff_factor=1,
52
- status_forcelist=status_forcelist)))
53
- if not self.conn.bucket_exists(bucket):
54
- raise Exception(f"Test bucket {bucket} does not exist.")
55
- self.pb_args = ('-B', '/' + self.path, f'--s3={s3_type}')
56
- return
57
-
58
- def list_instance_backups(self, instance):
59
- full_path = os.path.join(self.path, 'backups', instance)
60
- candidates = self.conn.list_objects(bucket, prefix=full_path, recursive=True)
61
- return [os.path.basename(os.path.dirname(x.object_name))
62
- for x in candidates if x.object_name.endswith('backup.control')]
63
-
64
- def list_files(self, sub_dir, recursive=False):
65
- full_path = os.path.join(self.path, sub_dir)
66
- # Need '/' in the end to find inside the folder
67
- full_path_dir = full_path if full_path[-1] == '/' else full_path + '/'
68
- object_list = self.conn.list_objects(bucket, prefix=full_path_dir, recursive=recursive)
69
- return [obj.object_name.replace(full_path_dir, '', 1)
70
- for obj in object_list
71
- if not obj.is_dir]
72
-
73
- def list_dirs(self, sub_dir):
74
- full_path = os.path.join(self.path, sub_dir)
75
- # Need '/' in the end to find inside the folder
76
- full_path_dir = full_path if full_path[-1] == '/' else full_path + '/'
77
- object_list = self.conn.list_objects(bucket, prefix=full_path_dir, recursive=False)
78
- return [obj.object_name.replace(full_path_dir, '', 1).rstrip('\\/')
79
- for obj in object_list
80
- if obj.is_dir]
81
-
82
- def read_file(self, sub_path, *, text=True):
83
- full_path = os.path.join(self.path, sub_path)
84
- bytes = self.conn.get_object(bucket, full_path).read()
85
- if not text:
86
- return bytes
87
- return bytes.decode('utf-8')
88
-
89
- def write_file(self, sub_path, data, *, text=True):
90
- full_path = os.path.join(self.path, sub_path)
91
- if text:
92
- data = data.encode('utf-8')
93
- self.conn.put_object(bucket, full_path, io.BytesIO(data), length=len(data))
94
-
95
- def cleanup(self):
96
- self.remove_dir('')
97
-
98
- def remove_file(self, sub_path):
99
- full_path = os.path.join(self.path, sub_path)
100
- self.conn.remove_object(bucket, full_path)
101
-
102
- def remove_dir(self, sub_path):
103
- if sub_path:
104
- full_path = os.path.join(self.path, sub_path)
105
- else:
106
- full_path = self.path
107
- objs = self.conn.list_objects(bucket, prefix=full_path, recursive=True,
108
- include_version=True)
109
- delobjs = (DeleteObject(o.object_name, o.version_id) for o in objs)
110
- errs = list(self.conn.remove_objects(bucket, delobjs))
111
- if errs:
112
- strerrs = "; ".join(str(err) for err in errs)
113
- raise Exception("There were errors: {0}".format(strerrs))
114
-
115
- def exists(self, sub_path):
116
- full_path = os.path.join(self.path, sub_path)
117
- try:
118
- self.conn.stat_object(bucket, full_path)
119
- return True
120
- except minio.error.S3Error as s3err:
121
- if s3err.code == 'NoSuchKey':
122
- return False
123
- raise s3err
124
- except Exception as err:
125
- raise err
126
-
127
- def __str__(self):
128
- return '/' + self.path
129
-
130
- def __repr__(self):
131
- return "S3TestBackupDir" + str(self.path)
132
-
133
- def __fspath__(self):
134
- return self.path