mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-26 00:00:54 +08:00
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:
parent
11ef10332e
commit
267a13ec98
@ -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);
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
||||
|
@ -8,6 +8,7 @@ set(SOURCES
|
||||
|
||||
if (PG_VERSION_SUPPORTS_MULTINODE)
|
||||
list(APPEND SOURCES
|
||||
test_dist_util.c
|
||||
data_node.c
|
||||
deparse.c)
|
||||
endif ()
|
||||
|
50
tsl/test/src/test_dist_util.c
Normal file
50
tsl/test/src/test_dist_util.c
Normal 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));
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user