Fix data node extension version check

Currently, if the major version of the extension on the access node is
later than the version of the extension on the data node, the data node
is accepted. Since major versions are not compatible, it should not be
accepted.

Changed the check to only accept the data node if:
- The major version is the same on the data node and the access node.
- The minor version on the data node is same or earlier than than
  access node.

In addition, the code will print a warning if the version on the data
node is older than the version on the access node.
This commit is contained in:
Mats Kindahl 2019-11-28 10:09:54 +01:00 committed by Erik Nordström
parent 11ef10332e
commit 267a13ec98
9 changed files with 199 additions and 50 deletions

View File

@ -296,27 +296,54 @@ validate_data_node_settings(void)
MaxConnections)));
}
int
dist_util_version_compare(const char *lhs, const char *rhs)
/*
* Check that the data node version is compatible with the version on this
* node by checking that all of the following are true:
*
* - The major version is identical on the data node and the access node.
* - The minor version on the data node is before or the same as on the access
* node.
*
* We explicitly do *not* check the patch version since changes between patch
* versions will only fix bugs and there should be no problem using an older
* patch version of the extension on the data node.
*
* We also check if the version on the data node is older and set
* `old_version` to `true` or `false` so that caller can print a warning.
*/
bool
dist_util_is_compatible_version(const char *data_node_version, const char *access_node_version,
bool *is_old_version)
{
unsigned int lhs_major, lhs_minor, lhs_patch;
unsigned int rhs_major, rhs_minor, rhs_patch;
unsigned int data_node_major, data_node_minor, data_node_patch;
unsigned int access_node_major, access_node_minor, access_node_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)));
Assert(is_old_version);
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 (sscanf(data_node_version,
"%u.%u.%u",
&data_node_major,
&data_node_minor,
&data_node_patch) != 3)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("invalid data node version %s", data_node_version)));
if (sscanf(access_node_version,
"%u.%u.%u",
&access_node_major,
&access_node_minor,
&access_node_patch) != 3)
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("invalid access node version %s", access_node_version)));
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;
if (data_node_major == access_node_major)
if (data_node_minor == access_node_minor)
*is_old_version = (data_node_patch < access_node_patch);
else
*is_old_version = (data_node_minor < access_node_minor);
else
*is_old_version = (data_node_major < access_node_major);
return (data_node_major == access_node_major) && (data_node_minor <= access_node_minor);
}

View File

@ -32,6 +32,7 @@ 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);
bool dist_util_is_compatible_version(const char *data_node_version, const char *access_node_version,
bool *is_old_version);
#endif /* TIMESCALEDB_TSL_CHUNK_API_H */

View File

@ -851,8 +851,8 @@ bool
remote_connection_check_extension(TSConnection *conn, const char **owner_name, Oid *owner_oid)
{
PGresult *res;
int rc;
const char *data_node_version;
bool old_version;
res = remote_connection_execf(conn,
"SELECT usename, extowner, extversion FROM pg_extension JOIN "
@ -878,13 +878,20 @@ remote_connection_check_extension(TSConnection *conn, const char **owner_name, O
break;
}
/* compare extension version */
/* check extension version on data node and make sure that it is
* compatible */
data_node_version = PQgetvalue(res, 0, 2);
rc = dist_util_version_compare(data_node_version, TIMESCALEDB_VERSION_MOD);
if (rc < 0)
ereport(WARNING,
if (!dist_util_is_compatible_version(data_node_version, TIMESCALEDB_VERSION, &old_version))
ereport(ERROR,
(errcode(ERRCODE_TS_DATA_NODE_INVALID_CONFIG),
errmsg("data node \"%s\" has an outdated timescaledb extension version",
errmsg("data node \"%s\" has an incompatible timescaledb extension version",
NameStr(conn->node_name)),
errdetail_internal("Access node version: %s, data node version: %s.",
TIMESCALEDB_VERSION_MOD,
data_node_version)));
if (old_version)
ereport(WARNING,
(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,

View File

@ -1252,24 +1252,10 @@ END
$BODY$;
CREATE EXTENSION timescaledb VERSION '0.0.0';
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER;
\set ON_ERROR_STOP 0
SELECT * FROM add_data_node('data_node_1', 'localhost', database => 'data_node_1',
bootstrap => false);
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');
WARNING: only one data node was assigned to the hypertable
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
ERROR: data node "data_node_1" has an incompatible timescaledb extension version
\set ON_ERROR_STOP 1
RESET ROLE;
DROP DATABASE data_node_1;

View File

@ -11,6 +11,67 @@ DROP DATABASE IF EXISTS frontend_1;
DROP DATABASE IF EXISTS frontend_2;
SET client_min_messages TO NOTICE;
----------------------------------------------------------------
-- Test version compability function
CREATE OR REPLACE FUNCTION compatible_version(version CSTRING, reference CSTRING)
RETURNS TABLE(is_compatible BOOLEAN, is_old_version BOOLEAN)
AS :TSL_MODULE_PATHNAME, 'ts_test_compatible_version'
LANGUAGE C VOLATILE;
SELECT * FROM compatible_version('2.0.0-beta3.19', reference => '2.0.0-beta3.19');
is_compatible | is_old_version
---------------+----------------
t | f
(1 row)
SELECT * FROM compatible_version('2.0.0', reference => '2.0.0');
is_compatible | is_old_version
---------------+----------------
t | f
(1 row)
SELECT * FROM compatible_version('1.9.9', reference => '2.0.0-beta3.19');
is_compatible | is_old_version
---------------+----------------
f | t
(1 row)
SELECT * FROM compatible_version('1.9.9', reference => '2.0.0');
is_compatible | is_old_version
---------------+----------------
f | t
(1 row)
SELECT * FROM compatible_version('2.0.9', reference => '2.0.0-beta3.19');
is_compatible | is_old_version
---------------+----------------
t | f
(1 row)
SELECT * FROM compatible_version('2.0.9', reference => '2.0.0');
is_compatible | is_old_version
---------------+----------------
t | f
(1 row)
SELECT * FROM compatible_version('2.1.9', reference => '2.0.0-beta3.19');
is_compatible | is_old_version
---------------+----------------
f | f
(1 row)
SELECT * FROM compatible_version('2.1.0', reference => '2.1.19-beta3.19');
is_compatible | is_old_version
---------------+----------------
t | t
(1 row)
-- These should not parse and instead generate an error.
\set ON_ERROR_STOP 0
SELECT * FROM compatible_version('2.1.*', reference => '2.1.19-beta3.19');
ERROR: invalid data node version 2.1.*
SELECT * FROM compatible_version('2.1.0', reference => '2.1.*');
ERROR: invalid access node version 2.1.*
\set ON_ERROR_STOP 1
----------------------------------------------------------------
-- Create two distributed databases
CREATE DATABASE frontend_1;
CREATE DATABASE frontend_2;

View File

@ -620,16 +620,9 @@ CREATE EXTENSION timescaledb VERSION '0.0.0';
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER;
\set ON_ERROR_STOP 0
SELECT * FROM add_data_node('data_node_1', 'localhost', database => 'data_node_1',
bootstrap => false);
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;

View File

@ -12,6 +12,29 @@ DROP DATABASE IF EXISTS frontend_1;
DROP DATABASE IF EXISTS frontend_2;
SET client_min_messages TO NOTICE;
----------------------------------------------------------------
-- Test version compability function
CREATE OR REPLACE FUNCTION compatible_version(version CSTRING, reference CSTRING)
RETURNS TABLE(is_compatible BOOLEAN, is_old_version BOOLEAN)
AS :TSL_MODULE_PATHNAME, 'ts_test_compatible_version'
LANGUAGE C VOLATILE;
SELECT * FROM compatible_version('2.0.0-beta3.19', reference => '2.0.0-beta3.19');
SELECT * FROM compatible_version('2.0.0', reference => '2.0.0');
SELECT * FROM compatible_version('1.9.9', reference => '2.0.0-beta3.19');
SELECT * FROM compatible_version('1.9.9', reference => '2.0.0');
SELECT * FROM compatible_version('2.0.9', reference => '2.0.0-beta3.19');
SELECT * FROM compatible_version('2.0.9', reference => '2.0.0');
SELECT * FROM compatible_version('2.1.9', reference => '2.0.0-beta3.19');
SELECT * FROM compatible_version('2.1.0', reference => '2.1.19-beta3.19');
-- These should not parse and instead generate an error.
\set ON_ERROR_STOP 0
SELECT * FROM compatible_version('2.1.*', reference => '2.1.19-beta3.19');
SELECT * FROM compatible_version('2.1.0', reference => '2.1.*');
\set ON_ERROR_STOP 1
----------------------------------------------------------------
-- Create two distributed databases

View File

@ -8,6 +8,7 @@ set(SOURCES
if (PG_VERSION_SUPPORTS_MULTINODE)
list(APPEND SOURCES
test_dist_util.c
data_node.c
deparse.c)
endif ()

View File

@ -0,0 +1,50 @@
/*
* This file and its contents are licensed under the Timescale License.
* Please see the included NOTICE for copyright information and
* LICENSE-TIMESCALE for a copy of the license.
*/
#include <postgres.h>
#include <fmgr.h>
#include <funcapi.h>
#include <export.h>
#include <access/htup_details.h>
#include "dist_util.h"
TS_FUNCTION_INFO_V1(ts_test_compatible_version);
Datum
ts_test_compatible_version(PG_FUNCTION_ARGS)
{
const char *checked_version = PG_GETARG_CSTRING(0);
const char *reference_version = PG_GETARG_CSTRING(1);
TupleDesc tupdesc;
HeapTuple tuple;
Datum values[2];
bool nulls[2] = { false };
bool is_old_version;
bool is_compatible;
if (PG_ARGISNULL(1))
reference_version = TIMESCALEDB_VERSION;
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning record called in context "
"that cannot accept type record")));
}
is_compatible =
dist_util_is_compatible_version(checked_version, reference_version, &is_old_version);
values[0] = BoolGetDatum(is_compatible);
values[1] = BoolGetDatum(is_old_version);
tuple = heap_form_tuple(tupdesc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}