Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f2df286

Browse files
authored
Merge pull request #531 from postgrespro/REL_2_5-PBCKP-236
[PBCKP-236] merge into REL_2_5
2 parents 97355f1 + 26f9992 commit f2df286

File tree

5 files changed

+231
-14
lines changed

5 files changed

+231
-14
lines changed

src/pg_probackup.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,8 @@ typedef enum ShowFormat
348348
#define PROGRAM_VERSION "2.5.8"
349349

350350
/* update when remote agent API or behaviour changes */
351-
#define AGENT_PROTOCOL_VERSION 20501
352-
#define AGENT_PROTOCOL_VERSION_STR "2.5.1"
351+
#define AGENT_PROTOCOL_VERSION 20509
352+
#define AGENT_PROTOCOL_VERSION_STR "2.5.9"
353353

354354
/* update only when changing storage format */
355355
#define STORAGE_FORMAT_VERSION "2.4.4"
@@ -888,6 +888,11 @@ extern bool tliIsPartOfHistory(const parray *timelines, TimeLineID tli);
888888
extern DestDirIncrCompatibility check_incremental_compatibility(const char *pgdata, uint64 system_identifier,
889889
IncrRestoreMode incremental_mode);
890890

891+
/* in remote.c */
892+
extern void check_remote_agent_compatibility(int agent_version,
893+
char *compatibility_str, size_t compatibility_str_max_size);
894+
extern size_t prepare_compatibility_str(char* compatibility_buf, size_t compatibility_buf_size);
895+
891896
/* in merge.c */
892897
extern void do_merge(InstanceState *instanceState, time_t backup_id, bool no_validate, bool no_sync);
893898
extern void merge_backups(pgBackup *backup, pgBackup *next_backup);

src/utils/file.c

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,17 +269,22 @@ fio_write_all(int fd, void const* buf, size_t size)
269269
}
270270

271271
/* Get version of remote agent */
272-
int
273-
fio_get_agent_version(void)
272+
void
273+
fio_get_agent_version(int* protocol, char* payload_buf, size_t payload_buf_size)
274274
{
275275
fio_header hdr;
276276
hdr.cop = FIO_AGENT_VERSION;
277277
hdr.size = 0;
278278

279279
IO_CHECK(fio_write_all(fio_stdout, &hdr, sizeof(hdr)), sizeof(hdr));
280280
IO_CHECK(fio_read_all(fio_stdin, &hdr, sizeof(hdr)), sizeof(hdr));
281+
if (hdr.size > payload_buf_size)
282+
{
283+
elog(ERROR, "Corrupted remote compatibility protocol: insufficient payload_buf_size=%zu", payload_buf_size);
284+
}
281285

282-
return hdr.arg;
286+
*protocol = hdr.arg;
287+
IO_CHECK(fio_read_all(fio_stdin, payload_buf, hdr.size), hdr.size);
283288
}
284289

285290
/* Open input stream. Remote file is fetched to the in-memory buffer and then accessed through Linux fmemopen */
@@ -3322,9 +3327,16 @@ fio_communicate(int in, int out)
33223327
IO_CHECK(fio_write_all(out, buf, hdr.size), hdr.size);
33233328
break;
33243329
case FIO_AGENT_VERSION:
3325-
hdr.arg = AGENT_PROTOCOL_VERSION;
3326-
IO_CHECK(fio_write_all(out, &hdr, sizeof(hdr)), sizeof(hdr));
3327-
break;
3330+
{
3331+
size_t payload_size = prepare_compatibility_str(buf, buf_size);
3332+
3333+
hdr.arg = AGENT_PROTOCOL_VERSION;
3334+
hdr.size = payload_size;
3335+
3336+
IO_CHECK(fio_write_all(out, &hdr, sizeof(hdr)), sizeof(hdr));
3337+
IO_CHECK(fio_write_all(out, buf, payload_size), payload_size);
3338+
break;
3339+
}
33283340
case FIO_STAT: /* Get information about file with specified path */
33293341
hdr.size = sizeof(st);
33303342
rc = hdr.arg ? stat(buf, &st) : lstat(buf, &st);

src/utils/file.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ extern fio_location MyLocation;
9191
extern void fio_redirect(int in, int out, int err);
9292
extern void fio_communicate(int in, int out);
9393

94-
extern int fio_get_agent_version(void);
94+
extern void fio_get_agent_version(int* protocol, char* payload_buf, size_t payload_buf_size);
9595
extern FILE* fio_fopen(char const* name, char const* mode, fio_location location);
9696
extern size_t fio_fwrite(FILE* f, void const* buf, size_t size);
9797
extern ssize_t fio_fwrite_async_compressed(FILE* f, void const* buf, size_t size, int compress_alg);

src/utils/remote.c

Lines changed: 124 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,113 @@ bool launch_agent(void)
238238
fio_redirect(infd[0], outfd[1], errfd[0]); /* write to stdout */
239239
}
240240

241-
/* Make sure that remote agent has the same version
242-
* TODO: we must also check PG version and fork edition
243-
*/
244-
agent_version = fio_get_agent_version();
241+
242+
/* Make sure that remote agent has the same version, fork and other features to be binary compatible */
243+
{
244+
char payload_buf[1024];
245+
fio_get_agent_version(&agent_version, payload_buf, sizeof payload_buf);
246+
check_remote_agent_compatibility(agent_version, payload_buf, sizeof payload_buf);
247+
}
248+
249+
return true;
250+
}
251+
252+
#ifdef PGPRO_EDITION
253+
/* PGPRO 10-13 checks to be "(certified)", with exceptional case PGPRO_11 conforming to "(standard certified)" */
254+
static bool check_certified()
255+
{
256+
return strstr(PGPRO_VERSION_STR, "(certified)") ||
257+
strstr(PGPRO_VERSION_STR, "(standard certified)");
258+
}
259+
#endif
260+
261+
static char* extract_pg_edition_str()
262+
{
263+
static char *vanilla = "vanilla";
264+
#ifdef PGPRO_EDITION
265+
static char *_1C = "1C";
266+
static char *std = "standard";
267+
static char *ent = "enterprise";
268+
static char *std_cert = "standard-certified";
269+
static char *ent_cert = "enterprise-certified";
270+
271+
if (strcmp(PGPRO_EDITION, _1C) == 0)
272+
return vanilla;
273+
274+
if (PG_VERSION_NUM < 100000)
275+
return PGPRO_EDITION;
276+
277+
/* these "certified" checks are applicable to PGPRO from 10 up to 12 versions.
278+
* 13+ certified versions are compatible to non-certified ones */
279+
if (PG_VERSION_NUM < 130000 && check_certified())
280+
{
281+
if (strcmp(PGPRO_EDITION, std) == 0)
282+
return std_cert;
283+
else if (strcmp(PGPRO_EDITION, ent) == 0)
284+
return ent_cert;
285+
else
286+
Assert("Bad #define PGPRO_EDITION value" == 0);
287+
}
288+
289+
return PGPRO_EDITION;
290+
#else
291+
return vanilla;
292+
#endif
293+
}
294+
295+
#define COMPATIBILITY_VAL_STR(macro) { #macro, macro, 0 }
296+
#define COMPATIBILITY_VAL_INT(macro) { #macro, NULL, macro }
297+
298+
#define COMPATIBILITY_VAL_SEPARATOR "="
299+
#define COMPATIBILITY_LINE_SEPARATOR "\n"
300+
301+
/*
302+
* Compose compatibility string to be sent by pg_probackup agent
303+
* through ssh and to be verified by pg_probackup peer.
304+
* Compatibility string contains postgres essential vars as strings
305+
* in format "var_name" + COMPATIBILITY_VAL_SEPARATOR + "var_value" + COMPATIBILITY_LINE_SEPARATOR
306+
*/
307+
size_t prepare_compatibility_str(char* compatibility_buf, size_t compatibility_buf_size)
308+
{
309+
typedef struct compatibility_param_tag {
310+
const char* name;
311+
const char* strval;
312+
int intval;
313+
} compatibility_param;
314+
315+
compatibility_param compatibility_params[] = {
316+
COMPATIBILITY_VAL_STR(PG_MAJORVERSION),
317+
{ "edition", extract_pg_edition_str(), 0 },
318+
COMPATIBILITY_VAL_INT(SIZEOF_VOID_P),
319+
};
320+
321+
size_t result_size = 0;
322+
*compatibility_buf = '\0';
323+
324+
for (int i = 0; i < (sizeof compatibility_params / sizeof(compatibility_param)); i++)
325+
{
326+
if (compatibility_params[i].strval != NULL)
327+
result_size += snprintf(compatibility_buf + result_size, compatibility_buf_size - result_size,
328+
"%s" COMPATIBILITY_VAL_SEPARATOR "%s" COMPATIBILITY_LINE_SEPARATOR,
329+
compatibility_params[i].name,
330+
compatibility_params[i].strval);
331+
else
332+
result_size += snprintf(compatibility_buf + result_size, compatibility_buf_size - result_size,
333+
"%s" COMPATIBILITY_VAL_SEPARATOR "%d" COMPATIBILITY_LINE_SEPARATOR,
334+
compatibility_params[i].name,
335+
compatibility_params[i].intval);
336+
Assert(result_size < compatibility_buf_size);
337+
}
338+
return result_size + 1;
339+
}
340+
341+
/*
342+
* Check incoming remote agent's compatibility params for equality to local ones.
343+
*/
344+
void check_remote_agent_compatibility(int agent_version, char *compatibility_str, size_t compatibility_str_max_size)
345+
{
346+
elog(LOG, "Agent version=%d\n", agent_version);
347+
245348
if (agent_version != AGENT_PROTOCOL_VERSION)
246349
{
247350
char agent_version_str[1024];
@@ -255,5 +358,21 @@ bool launch_agent(void)
255358
agent_version_str, AGENT_PROTOCOL_VERSION_STR);
256359
}
257360

258-
return true;
361+
/* checking compatibility params */
362+
if (strnlen(compatibility_str, compatibility_str_max_size) == compatibility_str_max_size)
363+
{
364+
elog(ERROR, "Corrupted remote compatibility protocol: compatibility string has no terminating \\0");
365+
}
366+
367+
elog(LOG, "Agent compatibility params:\n%s", compatibility_str);
368+
369+
{
370+
char buf[1024];
371+
372+
prepare_compatibility_str(buf, sizeof buf);
373+
if(strcmp(compatibility_str, buf))
374+
{
375+
elog(ERROR, "Incompatible remote agent params, expected:\n%s, actual:\n:%s", buf, compatibility_str);
376+
}
377+
}
259378
}

tests/compatibility.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,89 @@
88
module_name = 'compatibility'
99

1010

11+
def check_manual_tests_enabled():
12+
return 'PGPROBACKUP_MANUAL' in os.environ and os.environ['PGPROBACKUP_MANUAL'] == 'ON'
13+
14+
15+
def check_ssh_agent_path_exists():
16+
return 'PGPROBACKUP_SSH_AGENT_PATH' in os.environ
17+
18+
1119
class CompatibilityTest(ProbackupTest, unittest.TestCase):
1220

21+
def setUp(self):
22+
self.fname = self.id().split('.')[3]
23+
24+
# @unittest.expectedFailure
25+
@unittest.skipUnless(check_manual_tests_enabled(), 'skip manual test')
26+
@unittest.skipUnless(check_ssh_agent_path_exists(), 'skip no ssh agent path exist')
27+
# @unittest.skip("skip")
28+
def test_catchup_with_different_remote_major_pg(self):
29+
"""
30+
Decription in jira issue PBCKP-236
31+
This test exposures ticket error using pg_probackup builds for both PGPROEE11 and PGPROEE9_6
32+
33+
Prerequisites:
34+
- pg_probackup git tag for PBCKP 2.5.1
35+
- master pg_probackup build should be made for PGPROEE11
36+
- agent pg_probackup build should be made for PGPROEE9_6
37+
38+
Calling probackup PGPROEE9_6 pg_probackup agent from PGPROEE11 pg_probackup master for DELTA backup causes
39+
the PBCKP-236 problem
40+
41+
Please give env variables PROBACKUP_MANUAL=ON;PGPROBACKUP_SSH_AGENT_PATH=<pg_probackup_ssh_agent_path>
42+
for the test
43+
44+
Please make path for agent's pgprobackup_ssh_agent_path = '/home/avaness/postgres/postgres.build.ee.9.6/bin/'
45+
without pg_probackup executable
46+
"""
47+
48+
self.verbose = True
49+
self.remote = True
50+
# please use your own local path like
51+
# pgprobackup_ssh_agent_path = '/home/avaness/postgres/postgres.build.clean/bin/'
52+
pgprobackup_ssh_agent_path = os.environ['PGPROBACKUP_SSH_AGENT_PATH']
53+
54+
src_pg = self.make_simple_node(
55+
base_dir=os.path.join(module_name, self.fname, 'src'),
56+
set_replication=True,
57+
)
58+
src_pg.slow_start()
59+
src_pg.safe_psql(
60+
"postgres",
61+
"CREATE TABLE ultimate_question AS SELECT 42 AS answer")
62+
63+
# do full catchup
64+
dst_pg = self.make_empty_node(os.path.join(module_name, self.fname, 'dst'))
65+
self.catchup_node(
66+
backup_mode='FULL',
67+
source_pgdata=src_pg.data_dir,
68+
destination_node=dst_pg,
69+
options=['-d', 'postgres', '-p', str(src_pg.port), '--stream']
70+
)
71+
72+
dst_options = {'port': str(dst_pg.port)}
73+
self.set_auto_conf(dst_pg, dst_options)
74+
dst_pg.slow_start()
75+
dst_pg.stop()
76+
77+
src_pg.safe_psql(
78+
"postgres",
79+
"CREATE TABLE ultimate_question2 AS SELECT 42 AS answer")
80+
81+
# do delta catchup with remote pg_probackup agent with another postgres major version
82+
# this DELTA backup should fail without PBCKP-236 patch.
83+
self.catchup_node(
84+
backup_mode='DELTA',
85+
source_pgdata=src_pg.data_dir,
86+
destination_node=dst_pg,
87+
# here's substitution of --remoge-path pg_probackup agent compiled with another postgres version
88+
options=['-d', 'postgres', '-p', str(src_pg.port), '--stream', '--remote-path=' + pgprobackup_ssh_agent_path]
89+
)
90+
91+
# Clean after yourself
92+
self.del_test_dir(module_name, self.fname)
93+
1394
# @unittest.expectedFailure
1495
# @unittest.skip("skip")
1596
def test_backward_compatibility_page(self):

0 commit comments

Comments
 (0)