From 314f2c64ed8d556bc911c2e0cf218b13020420fd Mon Sep 17 00:00:00 2001 From: Craig Ringer Date: Sat, 7 Feb 2015 00:29:55 +1300 Subject: [PATCH] bdr: add bdr_get_remote_nodeinfo(...) Add a utility function that permits a remote node to be probed for BDR identification information of interest. --- Makefile.in | 3 +- bdr.h | 27 +++ bdr_init_replica.c | 73 +++++--- bdr_remotecalls.c | 290 +++++++++++++++++++++++++++++++ expected/identifier.out | 33 ++++ extsql/bdr--0.8.0.7--0.9.0.0.sql | 17 ++ sql/identifier.sql | 21 +++ 7 files changed, 435 insertions(+), 29 deletions(-) create mode 100644 bdr_remotecalls.c diff --git a/Makefile.in b/Makefile.in index b8b337567e..e92e62f18e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -54,7 +54,8 @@ OBJS = \ bdr_label.o \ bdr_locks.o \ bdr_output.o \ - bdr_relcache.o + bdr_relcache.o \ + bdr_remotecalls.o ifeq "@BUILDING_BDR@" "1" OBJS += \ diff --git a/bdr.h b/bdr.h index ef634e791e..00b1056692 100644 --- a/bdr.h +++ b/bdr.h @@ -467,6 +467,12 @@ bdr_establish_connection_and_slot(BdrConnectionConfig *cfg, RepNodeId *out_replication_identifier, char **out_snapshot); +extern PGconn* bdr_connect_nonrepl(const char *connstring, + const char *appnamesuffix); + +/* Helper for PG_ENSURE_ERROR_CLEANUP to close a PGconn */ +extern void bdr_cleanup_conn_close(int code, Datum offset); + /* use instead of heap_open()/heap_close() */ extern BDRRelation *bdr_heap_open(Oid reloid, LOCKMODE lockmode); extern void bdr_heap_close(BDRRelation * rel, LOCKMODE lockmode); @@ -492,6 +498,27 @@ extern HeapTuple bdr_conflict_handlers_resolve(BDRRelation * rel, /* replication set stuff */ void bdr_validate_replication_set_name(const char *name, bool allow_implicit); +/* Helpers to probe remote nodes */ + +typedef struct remote_node_info +{ + uint64 sysid; + char *sysid_str; + TimeLineID timeline; + Oid dboid; + char *variant; + char *version; + int version_num; + int min_remote_version_num; + bool is_superuser; +} remote_node_info; + +extern void bdr_get_remote_nodeinfo_internal(PGconn *conn, remote_node_info *ri); + +extern void free_remote_node_info(remote_node_info *ri); + +extern void bdr_ensure_ext_installed(PGconn *pgconn); + /* * Global to identify the type of BDR worker the current process is. Primarily * useful for assertions and debugging. diff --git a/bdr_init_replica.c b/bdr_init_replica.c index 5b5d294c74..9cc134b795 100644 --- a/bdr_init_replica.c +++ b/bdr_init_replica.c @@ -25,6 +25,7 @@ #include "bdr.h" #include "fmgr.h" +#include "funcapi.h" #include "libpq-fe.h" #include "miscadmin.h" @@ -332,20 +333,18 @@ bdr_get_remote_lsn(PGconn *conn) return lsn; } -/* - * Make sure the bdr extension is installed on the other end. If it's a known - * extension but not present in the current DB error out and tell the user to - * activate BDR then try again. - */ static void -bdr_ensure_ext_installed(PGconn *pgconn, Name bdr_conn_name) +bdr_get_remote_ext_version(PGconn *pgconn, char **default_version, + char **installed_version) { PGresult *res; + const char *q_bdr_installed = "SELECT default_version, installed_version " "FROM pg_catalog.pg_available_extensions WHERE name = 'bdr';"; res = PQexec(pgconn, q_bdr_installed); + if (PQresultStatus(res) != PGRES_TUPLES_OK) { elog(ERROR, "Unable to get remote bdr extension version; query %s failed with %s: %s\n", @@ -354,38 +353,57 @@ bdr_ensure_ext_installed(PGconn *pgconn, Name bdr_conn_name) if (PQntuples(res) == 1) { - char *default_version PG_USED_FOR_ASSERTS_ONLY; /* * bdr ext is known to Pg, check install state. - * - * Right now we don't check the installed version or try to install/upgrade. */ - default_version = PQgetvalue(res, 0, 0); - Assert(default_version != NULL); - if (PQgetisnull(res, 0, 1)) - { - ereport(ERROR, - (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("Remote database for BDR connection %s does not have the bdr extension active", - NameStr(*bdr_conn_name)), - errdetail("no entry with name 'bdr' in pg_extensions"), - errhint("add 'bdr' to shared_preload_libraries in postgresql.conf " - "on the target server and restart it."))); - } + *default_version = pstrdup(PQgetvalue(res, 0, 0)); + *installed_version = pstrdup(PQgetvalue(res, 0, 0)); } else if (PQntuples(res) == 0) { /* bdr ext is not known to Pg at all */ - ereport(ERROR, - (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("Remote PostgreSQL install for bdr connection %s does not have bdr extension installed", - NameStr(*bdr_conn_name)), - errdetail("no entry with name 'bdr' in pg_available_extensions; did you install BDR?"))); } else { Assert(false); /* Should not get >1 tuples */ } + + PQclear(res); +} + +/* + * Make sure the bdr extension is installed on the other end. If it's a known + * extension but not present in the current DB error out and tell the user to + * activate BDR then try again. + */ +void +bdr_ensure_ext_installed(PGconn *pgconn) +{ + char *default_version = NULL; + char *installed_version = NULL; + + bdr_get_remote_ext_version(pgconn, &default_version, &installed_version); + + if (default_version == NULL || strcmp(default_version, "") == 0) + { + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("Remote PostgreSQL install for bdr connection does not have bdr extension installed"), + errdetail("no entry with name 'bdr' in pg_available_extensions."), + errhint("You need to install the BDR extension on the remote end"))); + } + + if (installed_version == NULL || strcmp(installed_version, "") == 0) + { + ereport(ERROR, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("Remote database for BDR connection does not have the bdr extension active"), + errdetail("installed_version for entry 'bdr' in pg_available_extensions is blank"), + errhint("Run 'CREATE EXTENSION bdr;'"))); + } + + pfree(default_version); + pfree(installed_version); } @@ -695,7 +713,6 @@ bdr_init_replica_conn_close(int code, Datum connptr) PQfinish(conn); } - /* * Determine whether we need to initialize the database from a remote * node and perform the required initialization if so. @@ -827,7 +844,7 @@ bdr_init_replica(Name dbname) PG_ENSURE_ERROR_CLEANUP(bdr_init_replica_conn_close, PointerGetDatum(&nonrepl_init_conn)); { - bdr_ensure_ext_installed(nonrepl_init_conn, dbname); + bdr_ensure_ext_installed(nonrepl_init_conn); /* Get the bdr.bdr_nodes status field for our node id from the remote */ status = bdr_get_remote_status(nonrepl_init_conn); diff --git a/bdr_remotecalls.c b/bdr_remotecalls.c new file mode 100644 index 0000000000..108cd0a739 --- /dev/null +++ b/bdr_remotecalls.c @@ -0,0 +1,290 @@ +/* ------------------------------------------------------------------------- + * + * bdr_remotecalls.c + * Make libpq requests to a remote BDR instance + * + * Copyright (C) 2012-2015, PostgreSQL Global Development Group + * + * IDENTIFICATION + * bdr_remotecalls.c + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "bdr.h" + +#include "fmgr.h" +#include "funcapi.h" +#include "libpq-fe.h" +#include "miscadmin.h" + +#include "libpq/pqformat.h" + +#include "access/heapam.h" +#include "access/xact.h" + +#include "catalog/pg_type.h" + +#include "executor/spi.h" + +#include "bdr_replication_identifier.h" +#include "replication/walreceiver.h" + +#include "postmaster/bgworker.h" +#include "postmaster/bgwriter.h" + +#include "storage/ipc.h" +#include "storage/latch.h" +#include "storage/lwlock.h" +#include "storage/proc.h" +#include "storage/shmem.h" + +#include "utils/builtins.h" +#include "utils/pg_lsn.h" +#include "utils/syscache.h" + +PGDLLEXPORT Datum bdr_get_remote_nodeinfo(PG_FUNCTION_ARGS); + +PG_FUNCTION_INFO_V1(bdr_get_remote_nodeinfo); + +/* + * Make standard postgres connection, ERROR on failure. + */ +PGconn* +bdr_connect_nonrepl(const char *connstring, const char *appnamesuffix) +{ + PGconn *nonrepl_conn; + StringInfoData dsn; + + initStringInfo(&dsn); + appendStringInfo(&dsn, + "%s fallback_application_name='"BDR_LOCALID_FORMAT":%s'", + connstring, BDR_LOCALID_FORMAT_ARGS, appnamesuffix); + + /* + * Test to see if there's an entry in the remote's bdr.bdr_nodes for our + * system identifier. If there is, that'll tell us what stage of startup + * we are up to and let us resume an incomplete start. + */ + nonrepl_conn = PQconnectdb(dsn.data); + if (PQstatus(nonrepl_conn) != CONNECTION_OK) + { + ereport(FATAL, + (errmsg("could not connect to the server in non-replication mode: %s", + PQerrorMessage(nonrepl_conn)), + errdetail("dsn was: %s", dsn.data))); + } + + return nonrepl_conn; +} + +/* + * Close a connection if it exists. The connection passed + * is a pointer to a *PGconn; if the target is NULL, it's + * presumed not inited or already closed and is ignored. + */ +void +bdr_cleanup_conn_close(int code, Datum connptr) +{ + PGconn **conn_p; + PGconn *conn; + + conn_p = (PGconn**) DatumGetPointer(connptr); + Assert(conn_p != NULL); + conn = *conn_p; + + if (conn == NULL) + return; + if (PQstatus(conn) != CONNECTION_OK) + return; + PQfinish(conn); +} + +/* + * Frees contents of a remote_node_info (but not the struct its self) + */ +void +free_remote_node_info(remote_node_info *ri) +{ + pfree(ri->sysid_str); + pfree(ri->variant); + pfree(ri->version); +} + +/* + * The implementation guts of bdr_get_remote_nodeinfo, callable with + * a pre-existing connection. + */ +void +bdr_get_remote_nodeinfo_internal(PGconn *conn, struct remote_node_info *ri) +{ + PGresult *res, *res2; + int i; + char *remote_bdr_version_str; + int parsed_version_num; + + /* Make sure BDR is actually present and active on the remote */ + bdr_ensure_ext_installed(conn); + + /* Acquire sysid, timeline, dboid, version and variant */ + res = PQexec(conn, "SELECT sysid, timeline, dboid, " + "bdr.bdr_variant() AS variant, " + "bdr.bdr_version() AS version, " + "current_setting('is_superuser') AS issuper " + "FROM bdr.bdr_get_local_nodeid()"); + + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + ereport(ERROR, + (errmsg("getting remote node id failed"), + errdetail("SELECT sysid, timeline, dboid FROM bdr.bdr_get_local_nodeid() failed with: %s", + PQerrorMessage(conn)))); + } + + Assert(PQnfields(res) == 6); + + if (PQntuples(res) != 1) + elog(ERROR, "Got %d tuples instead of expected 1", PQntuples(res)); + + for (i = 0; i < 6; i++) + { + if (PQgetisnull(res, 0, i)) + elog(ERROR, "Unexpectedly null field %s", PQfname(res, i)); + } + + ri->sysid_str = pstrdup(PQgetvalue(res, 0, 0)); + + if (sscanf(ri->sysid_str, UINT64_FORMAT, &ri->sysid) != 1) + elog(ERROR, "could not parse remote sysid %s", ri->sysid_str); + + ri->timeline = DatumGetObjectId( + DirectFunctionCall1(oidin, CStringGetDatum(PQgetvalue(res, 0, 1)))); + ri->dboid = DatumGetObjectId( + DirectFunctionCall1(oidin, CStringGetDatum(PQgetvalue(res, 0, 2)))); + ri->variant = pstrdup(PQgetvalue(res, 0, 3)); + remote_bdr_version_str = PQgetvalue(res, 0, 4); + ri->version = pstrdup(remote_bdr_version_str); + /* To be filled later: numeric version, min remote version */ + ri->is_superuser = DatumGetBool( + DirectFunctionCall1(boolin, CStringGetDatum(PQgetvalue(res, 0, 5)))); + + /* + * Even though we should be able to get it from bdr_version_num, always + * parse the BDR version so that the parse code gets sanity checked. + */ + parsed_version_num = bdr_parse_version(remote_bdr_version_str, NULL, NULL, + NULL, NULL); + + /* + * If bdr_version_num() is present then the remote version is greater + * than 0.8.0 and we can safely use it and bdr_min_remote_version_num() + */ + res2 = PQexec(conn, "SELECT 1 FROM pg_proc p " + "INNER JOIN pg_namespace n ON (p.pronamespace = n.oid) " + "WHERE n.nspname = 'bdr' AND p.proname = 'bdr_version_num';"); + + if (PQresultStatus(res2) != PGRES_TUPLES_OK) + { + ereport(ERROR, + (errmsg("getting remote available functions failed"), + errdetail("Querying remote failed with: %s", PQerrorMessage(conn)))); + } + + Assert(PQnfields(res2) == 1); + Assert(PQntuples(res2) == 0 || PQntuples(res2) == 1); + + if (PQntuples(res2) == 1) + { + /* + * Can safely query for numeric version and min remote version + */ + PQclear(res2); + + res2 = PQexec(conn, "SELECT bdr.bdr_version_num(), " + " bdr.bdr_min_remote_version_num();"); + + if (PQresultStatus(res2) != PGRES_TUPLES_OK) + { + ereport(ERROR, + (errmsg("getting remote numeric BDR version failed"), + errdetail("Querying remote failed with: %s", PQerrorMessage(conn)))); + } + + Assert(PQnfields(res2) == 2); + Assert(PQntuples(res2) == 1); + + ri->version_num = atoi(PQgetvalue(res2, 0, 0)); + ri->min_remote_version_num = atoi(PQgetvalue(res2, 0, 1)); + + if (ri->version_num != parsed_version_num) + elog(WARNING, "parsed bdr version %d from string %s != returned bdr version %d", + parsed_version_num, remote_bdr_version_str, ri->version_num); + + PQclear(res2); + } + else + { + /* + * Must be an old version, can't get numeric version. + * + * All supported versions prior to introduction of bdr_version_num() + * have a min_remote_version_num of 000700, we can safely report that. + * For the version we have to parse it from the text version. + */ + PQclear(res2); + + /* Shouldn't happen, but as a sanity check: */ + if (parsed_version_num > 900) + elog(ERROR, "Remote BDR version reported as %s (n=%d) but bdr.bdr_version_num() missing", + remote_bdr_version_str, parsed_version_num); + + ri->version_num = parsed_version_num; + ri->min_remote_version_num = 700; + } + + PQclear(res); +} + +Datum +bdr_get_remote_nodeinfo(PG_FUNCTION_ARGS) +{ + const char *remote_node_dsn = text_to_cstring(PG_GETARG_TEXT_P(0)); + Datum values[8]; + bool isnull[8] = {false, false, false, false, false, false, false, false}; + TupleDesc tupleDesc; + HeapTuple returnTuple; + PGconn *conn; + + if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + conn = bdr_connect_nonrepl(remote_node_dsn, "bdrnodeinfo"); + + PG_ENSURE_ERROR_CLEANUP(bdr_cleanup_conn_close, + PointerGetDatum(&conn)); + { + struct remote_node_info ri; + + bdr_get_remote_nodeinfo_internal(conn, &ri); + + values[0] = CStringGetTextDatum(ri.sysid_str); + values[1] = ObjectIdGetDatum(ri.timeline); + values[2] = ObjectIdGetDatum(ri.dboid); + values[3] = CStringGetTextDatum(ri.variant); + values[4] = CStringGetTextDatum(ri.version); + values[5] = Int32GetDatum(ri.version_num); + values[6] = Int32GetDatum(ri.min_remote_version_num); + values[7] = BoolGetDatum(ri.is_superuser); + + returnTuple = heap_form_tuple(tupleDesc, values, isnull); + + free_remote_node_info(&ri); + } + PG_END_ENSURE_ERROR_CLEANUP(bdr_cleanup_conn_close, + PointerGetDatum(&conn)); + + PQfinish(conn); + + PG_RETURN_DATUM(HeapTupleGetDatum(returnTuple)); +} diff --git a/expected/identifier.out b/expected/identifier.out index 2d2e38b131..1564cddd82 100644 --- a/expected/identifier.out +++ b/expected/identifier.out @@ -6,3 +6,36 @@ FROM bdr.bdr_get_local_nodeid(); t | t (1 row) +SELECT current_database() = 'regression'; + ?column? +---------- + t +(1 row) + +-- Test probing for remote node information +SELECT + r.sysid = l.sysid, + r.timeline = l.timeline, + r.dboid = (SELECT oid FROM pg_database WHERE datname = 'postgres'), + variant = bdr.bdr_variant(), + version = bdr.bdr_version(), + version_num = bdr.bdr_version_num(), + min_remote_version_num = bdr.bdr_min_remote_version_num(), + is_superuser = 't' +FROM bdr.bdr_get_remote_nodeinfo('dbname=postgres') r, + bdr.bdr_get_local_nodeid() l; + ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? | ?column? +----------+----------+----------+----------+----------+----------+----------+---------- + t | t | t | t | t | t | t | t +(1 row) + +-- bdr.bdr_get_remote_nodeinfo can also be used to probe the local dsn +-- and make sure it works. +SELECT + r.dboid = (SELECT oid FROM pg_database WHERE datname = current_database()) +FROM bdr.bdr_get_remote_nodeinfo('dbname='||current_database()) r; + ?column? +---------- + t +(1 row) + diff --git a/extsql/bdr--0.8.0.7--0.9.0.0.sql b/extsql/bdr--0.8.0.7--0.9.0.0.sql index 4395a2f22d..4feaf86e97 100644 --- a/extsql/bdr--0.8.0.7--0.9.0.0.sql +++ b/extsql/bdr--0.8.0.7--0.9.0.0.sql @@ -1 +1,18 @@ -- BDR release 0.9.0.0 +SET LOCAL search_path = bdr; +SET LOCAL bdr.permit_unsafe_ddl_commands = true; +SET LOCAL bdr.skip_ddl_replication = true; + +CREATE FUNCTION bdr_get_remote_nodeinfo( + dsn text, sysid OUT text, timeline OUT oid, dboid OUT oid, + variant OUT text, version OUT text, version_num OUT integer, + min_remote_version_num OUT integer, is_superuser OUT boolean) +RETURNS record LANGUAGE c AS 'MODULE_PATHNAME'; + +REVOKE ALL ON FUNCTION bdr_get_remote_nodeinfo(text) FROM public; + +COMMENT ON FUNCTION bdr_get_remote_nodeinfo(text) IS 'Get node identity and BDR info from a remote server by dsn'; + +RESET bdr.permit_unsafe_ddl_commands; +RESET bdr.skip_ddl_replication; +RESET search_path; diff --git a/sql/identifier.sql b/sql/identifier.sql index 4d73aecf02..3627694745 100644 --- a/sql/identifier.sql +++ b/sql/identifier.sql @@ -1,3 +1,24 @@ -- No real way to test the sysid, so ignore it SELECT timeline= 1, dboid = (SELECT oid FROM pg_database WHERE datname = current_database()) FROM bdr.bdr_get_local_nodeid(); + +SELECT current_database() = 'regression'; + +-- Test probing for remote node information +SELECT + r.sysid = l.sysid, + r.timeline = l.timeline, + r.dboid = (SELECT oid FROM pg_database WHERE datname = 'postgres'), + variant = bdr.bdr_variant(), + version = bdr.bdr_version(), + version_num = bdr.bdr_version_num(), + min_remote_version_num = bdr.bdr_min_remote_version_num(), + is_superuser = 't' +FROM bdr.bdr_get_remote_nodeinfo('dbname=postgres') r, + bdr.bdr_get_local_nodeid() l; + +-- bdr.bdr_get_remote_nodeinfo can also be used to probe the local dsn +-- and make sure it works. +SELECT + r.dboid = (SELECT oid FROM pg_database WHERE datname = current_database()) +FROM bdr.bdr_get_remote_nodeinfo('dbname='||current_database()) r; -- 2.39.5