Check timescaledb extension version on a data node

Compare remote connection extension version with the one installed
on the access node. Show a warning message if it differs.

Check happens during add_data_node() call and after creating new
data node connection.
This commit is contained in:
Dmitry Simonenko 2019-09-11 14:22:01 +03:00 committed by Erik Nordström
parent 0c0e6b1070
commit 55d205b09b
11 changed files with 199 additions and 17 deletions

View File

@ -26,7 +26,6 @@
#include "catalog.h"
#include "extension.h"
#include "guc.h"
#include "config.h"
#include "extension_utils.c"
#include "compat.h"

View File

@ -12,5 +12,6 @@ install(
timescaledb--mock-3--mock-4.sql
timescaledb--mock-5--mock-6.sql
timescaledb--mock-broken--mock-5.sql
timescaledb--0.0.0.sql
DESTINATION "${PG_SHAREDIR}/extension")
endif (CMAKE_BUILD_TYPE MATCHES Debug)

View File

@ -0,0 +1,3 @@
-- This file and its contents are licensed under the Apache License 2.0.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.

View File

@ -377,19 +377,13 @@ data_node_bootstrap_extension(const char *node_name, const char *host, int32 por
const char *schema_name = ts_extension_schema_name();
const char *schema_name_quoted = quote_identifier(schema_name);
Oid schema_oid = get_namespace_oid(schema_name, true);
bool extension_exists = false;
bool extension_exists;
node_options = create_data_node_options(host, port, dbname, username);
conn = remote_connection_open_with_options(node_name, node_options, false);
res = remote_connection_execf(conn,
"SELECT 1 FROM pg_extension WHERE extname = %s",
quote_literal_cstr(EXTENSION_NAME));
if (PQntuples(res) > 0)
extension_exists = true;
remote_result_close(res);
/* Ensure the extension exists and has correct version */
extension_exists = remote_connection_check_extension(conn);
if (!extension_exists)
{

View File

@ -242,3 +242,28 @@ validate_data_node_settings(void)
max_prepared_xacts,
MaxConnections)));
}
int
dist_util_version_compare(const char *lhs, const char *rhs)
{
unsigned int lhs_major, lhs_minor, lhs_patch;
unsigned int rhs_major, rhs_minor, rhs_patch;
if (sscanf(lhs, "%u.%u.%u", &lhs_major, &lhs_minor, &lhs_patch) != 3)
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("invalid version %s", lhs)));
if (sscanf(rhs, "%u.%u.%u", &rhs_major, &rhs_minor, &rhs_patch) != 3)
ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("invalid version %s", rhs)));
if (lhs_major == rhs_major)
{
if (lhs_minor == rhs_minor)
{
if (lhs_patch == rhs_patch)
return 0;
return (lhs_patch < rhs_patch) ? -1 : 1;
}
return (lhs_minor < rhs_minor) ? -1 : 1;
}
return (lhs_major < rhs_major) ? -1 : 1;
}

View File

@ -31,5 +31,6 @@ bool dist_util_is_frontend_session(void);
Datum dist_util_remote_hypertable_info(PG_FUNCTION_ARGS);
void validate_data_node_settings(void);
int dist_util_version_compare(const char *lhs, const char *rhs);
#endif /* TIMESCALEDB_TSL_CHUNK_API_H */

View File

@ -40,6 +40,7 @@
#include "guc.h"
#include "utils.h"
#include "dist_util.h"
#include "errors.h"
/*
* Connection library for TimescaleDB.
@ -829,6 +830,46 @@ remote_result_query_ok(PGresult *res)
return remote_result_ok(res, PGRES_TUPLES_OK);
}
/*
* Check timescaledb extension version on a data node.
*
* Compare remote connection extension version with the one installed
* locally on the access node.
*
* Return false if extension is not found, true otherwise.
*/
bool
remote_connection_check_extension(TSConnection *conn)
{
PGresult *res;
int rc;
res = remote_connection_execf(conn,
"SELECT extversion FROM pg_extension WHERE extname = %s",
quote_literal_cstr(EXTENSION_NAME));
/* extension does not exists */
if (PQntuples(res) == 0)
{
PQclear(res);
return false;
}
/* compare extension version */
rc = dist_util_version_compare(PQgetvalue(res, 0, 0), TIMESCALEDB_VERSION_MOD);
if (rc < 0)
ereport(WARNING,
(errcode(ERRCODE_TS_DATA_NODE_INVALID_CONFIG),
errmsg("data node \"%s\" has an outdated timescaledb extension version",
NameStr(conn->node_name)),
errdetail_internal("Access node version: %s, data node version: %s.",
TIMESCALEDB_VERSION_MOD,
PQgetvalue(res, 0, 0))));
PQclear(res);
return true;
}
/*
* Configure remote connection using current instance UUID.
*
@ -1033,12 +1074,19 @@ remote_connection_open_with_options(const char *node_name, List *connection_opti
errmsg("could not configure remote connection to \"%s\"", node_name),
errdetail_internal("%s", PQerrorMessage(conn->pg_conn))));
/* Inform remote node about instance UUID */
if (set_dist_id && !remote_connection_set_peer_dist_id(conn))
ereport(ERROR,
(errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),
errmsg("could not set distributed ID for \"%s\"", node_name),
errdetail_internal("%s", PQerrorMessage(conn->pg_conn))));
/* Check a data node extension version and show a warning
* message if it differs */
remote_connection_check_extension(conn);
if (set_dist_id)
{
/* Inform remote node about instance UUID */
if (!remote_connection_set_peer_dist_id(conn))
ereport(ERROR,
(errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),
errmsg("could not set distributed ID for \"%s\"", node_name),
errdetail_internal("%s", PQerrorMessage(conn->pg_conn))));
}
}
PG_CATCH();
{

View File

@ -60,6 +60,7 @@ extern unsigned int remote_connection_get_cursor_number(void);
extern void remote_connection_reset_cursor_number(void);
extern unsigned int remote_connection_get_prep_stmt_number(void);
extern bool remote_connection_configure(TSConnection *conn);
extern bool remote_connection_check_extension(TSConnection *conn);
extern bool remote_connection_cancel_query(TSConnection *conn);
extern PGconn *remote_connection_get_pg_conn(TSConnection *conn);
extern bool remote_connection_is_processing(TSConnection *conn);

View File

@ -1235,6 +1235,62 @@ WARNING: new data for hypertable "disttable_4" will be under-replicated due to
SELECT * FROM add_data_node('data_node_6');
ERROR: function add_data_node(unknown) does not exist at character 15
\set ON_ERROR_STOP 1
--
-- Test timescale extension version check during add_data_node()
-- and create_distributed_hypertable() calls.
--
-- Use mock extension and create basic function wrappers to
-- establish connection to a data node.
--
RESET ROLE;
DROP DATABASE data_node_1;
CREATE DATABASE data_node_1 OWNER :ROLE_1;
\c data_node_1
CREATE SCHEMA _timescaledb_internal;
GRANT ALL ON SCHEMA _timescaledb_internal TO :ROLE_1;
CREATE FUNCTION _timescaledb_internal.set_dist_id(uuid UUID)
RETURNS BOOL LANGUAGE PLPGSQL AS
$BODY$
BEGIN
RETURN true;
END
$BODY$;
CREATE FUNCTION _timescaledb_internal.set_peer_dist_id(uuid UUID)
RETURNS BOOL LANGUAGE PLPGSQL AS
$BODY$
BEGIN
RETURN true;
END
$BODY$;
CREATE FUNCTION _timescaledb_internal.validate_as_data_node()
RETURNS BOOL LANGUAGE PLPGSQL AS
$BODY$
BEGIN
RETURN true;
END
$BODY$;
CREATE EXTENSION timescaledb VERSION '0.0.0';
\c :TEST_DBNAME :ROLE_SUPERUSER;
SELECT * FROM add_data_node('data_node_1', 'localhost', database => 'data_node_1',
if_not_exists => true);
NOTICE: database "data_node_1" already exists on data node, not creating it
WARNING: data node "data_node_1" has an outdated timescaledb extension version
WARNING: data node "data_node_1" has an outdated timescaledb extension version
WARNING: data node "data_node_1" has an outdated timescaledb extension version
node_name | host | port | database | node_created | database_created | extension_created
-------------+-----------+-------+-------------+--------------+------------------+-------------------
data_node_1 | localhost | 15432 | data_node_1 | t | f | f
(1 row)
GRANT USAGE ON FOREIGN SERVER data_node_1 TO :ROLE_1;
SET ROLE :ROLE_1;
CREATE TABLE test_disttable(time timestamptz);
\set ON_ERROR_STOP 0
SELECT * FROM create_distributed_hypertable('test_disttable', 'time');
NOTICE: adding not-null constraint to column "time"
WARNING: data node "data_node_1" has an outdated timescaledb extension version
ERROR: [data_node_1]: function public.create_hypertable(unknown, time_column_name => unknown, associated_schema_name => unknown, associated_table_prefix => unknown, chunk_time_interval => bigint, chunk_sizing_func => unknown, chunk_target_size => unknown, if_not_exists => boolean, migrate_data => boolean, create_default_indexes => boolean, replication_factor => integer) does not exist
\set ON_ERROR_STOP 1
RESET ROLE;
DROP DATABASE data_node_1;
DROP DATABASE data_node_2;

View File

@ -61,7 +61,7 @@ ERROR: bad query error thrown from test
SELECT * FROM test.get_connection_stats();
connections_created | connections_closed | results_created | results_cleared
---------------------+--------------------+-----------------+-----------------
1 | 1 | 6 | 6
1 | 1 | 7 | 7
(1 row)
SELECT test.remote_connection_tests();

View File

@ -575,8 +575,62 @@ SELECT * FROM delete_data_node('data_node_5', force =>true);
SELECT * FROM add_data_node('data_node_6');
\set ON_ERROR_STOP 1
--
-- Test timescale extension version check during add_data_node()
-- and create_distributed_hypertable() calls.
--
-- Use mock extension and create basic function wrappers to
-- establish connection to a data node.
--
RESET ROLE;
DROP DATABASE data_node_1;
CREATE DATABASE data_node_1 OWNER :ROLE_1;
\c data_node_1
CREATE SCHEMA _timescaledb_internal;
GRANT ALL ON SCHEMA _timescaledb_internal TO :ROLE_1;
CREATE FUNCTION _timescaledb_internal.set_dist_id(uuid UUID)
RETURNS BOOL LANGUAGE PLPGSQL AS
$BODY$
BEGIN
RETURN true;
END
$BODY$;
CREATE FUNCTION _timescaledb_internal.set_peer_dist_id(uuid UUID)
RETURNS BOOL LANGUAGE PLPGSQL AS
$BODY$
BEGIN
RETURN true;
END
$BODY$;
CREATE FUNCTION _timescaledb_internal.validate_as_data_node()
RETURNS BOOL LANGUAGE PLPGSQL AS
$BODY$
BEGIN
RETURN true;
END
$BODY$;
CREATE EXTENSION timescaledb VERSION '0.0.0';
\c :TEST_DBNAME :ROLE_SUPERUSER;
SELECT * FROM add_data_node('data_node_1', 'localhost', database => 'data_node_1',
if_not_exists => true);
GRANT USAGE ON FOREIGN SERVER data_node_1 TO :ROLE_1;
SET ROLE :ROLE_1;
CREATE TABLE test_disttable(time timestamptz);
\set ON_ERROR_STOP 0
SELECT * FROM create_distributed_hypertable('test_disttable', 'time');
\set ON_ERROR_STOP 1
RESET ROLE;
DROP DATABASE data_node_1;
DROP DATABASE data_node_2;
DROP DATABASE data_node_3;