bdr: add bdr_get_remote_nodeinfo(...)
authorCraig Ringer <craig@2ndquadrant.com>
Fri, 6 Feb 2015 11:29:55 +0000 (00:29 +1300)
committerAndres Freund <andres@anarazel.de>
Thu, 12 Feb 2015 09:16:58 +0000 (10:16 +0100)
Add a utility function that permits a remote node to be probed
for BDR identification information of interest.

Makefile.in
bdr.h
bdr_init_replica.c
bdr_remotecalls.c [new file with mode: 0644]
expected/identifier.out
extsql/bdr--0.8.0.7--0.9.0.0.sql
sql/identifier.sql

index b8b337567e23566a00cb096228e0ec1abf2c6674..e92e62f18ecd076948d5dd81898d03b9233c6e2d 100644 (file)
@@ -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 ef634e791ecc85c2e451a89cb6da56a78e9094d6..00b1056692c4e628c20cd3b060f41842785af077 100644 (file)
--- 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.
index 5b5d294c74b11ca2345cacc35a2495e23cfb379b..9cc134b795934cf2a0e06b21448b3cd5945847b9 100644 (file)
@@ -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 (file)
index 0000000..108cd0a
--- /dev/null
@@ -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));
+}
index 2d2e38b131e4427ae4d1a8f06474db15cafb0de9..1564cddd82169f90cc36042c1dfc621fbaf577e7 100644 (file)
@@ -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)
+
index 4395a2f22d73ac8c0a28427d20129557c452eb95..4feaf86e97aa6c8b7902e3f1f9f24eef2368192b 100644 (file)
@@ -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;
index 4d73aecf0265fd9fc85a99fb2f41235c2826c40b..36276947458a73b8412c5d89433d3231a97e5b0c 100644 (file)
@@ -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;