timescaledb/tsl/test/sql/dist_commands.sql
Erik Nordström 4b05402580 Add health check function
A new health check function _timescaledb_internal.health() returns the
health and status of the database instance, including any configured
data nodes (in case the instance is an access node).

Since the function returns also the health of the data nodes, it tries
hard to avoid throwing errors. An error will fail the whole function
and therefore not return any node statuses, although some of the nodes
might be healthy.

The health check on the data nodes is a recursive (remote) call to the
same function on those nodes. Unfortunately, the check will fail with
an error if a connection cannot be established to a node (or an error
occurs on the connection), which means the whole function call will
fail. This will be addressed in a future change by returning the error
in the function result instead.
2022-10-21 10:34:16 +02:00

281 lines
9.6 KiB
PL/PgSQL

-- 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.
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER
-- Support for execute_sql_and_filter_server_name_on_error()
\unset ECHO
\o /dev/null
\ir include/remote_exec.sql
\ir include/filter_exec.sql
\o
\set ECHO all
\set DATA_NODE_1 :TEST_DBNAME _1
\set DATA_NODE_2 :TEST_DBNAME _2
\set DATA_NODE_3 :TEST_DBNAME _3
SELECT node_name, database, node_created, database_created, extension_created
FROM (
SELECT (add_data_node(name, host => 'localhost', DATABASE => name)).*
FROM (VALUES (:'DATA_NODE_1'), (:'DATA_NODE_2'), (:'DATA_NODE_3')) v(name)
) a;
GRANT USAGE ON FOREIGN SERVER :DATA_NODE_1, :DATA_NODE_2, :DATA_NODE_3 TO PUBLIC;
\des
RESET ROLE;
CREATE FUNCTION _timescaledb_internal.invoke_distributed_commands()
RETURNS void
AS :TSL_MODULE_PATHNAME, 'ts_invoke_distributed_commands'
LANGUAGE C STRICT;
CREATE FUNCTION _timescaledb_internal.invoke_faulty_distributed_command()
RETURNS void
AS :TSL_MODULE_PATHNAME, 'ts_invoke_faulty_distributed_command'
LANGUAGE C STRICT;
SET ROLE :ROLE_1;
SELECT _timescaledb_internal.invoke_distributed_commands();
\c :DATA_NODE_1
\dt
SELECT * FROM disttable1;
\c :DATA_NODE_2
\dt
SELECT * FROM disttable1;
\c :DATA_NODE_3
\dt
SELECT * FROM disttable1;
\c :TEST_DBNAME :ROLE_SUPERUSER
SET ROLE :ROLE_1;
-- Verify failed insert command gets fully rolled back
\set ON_ERROR_STOP 0
SELECT _timescaledb_internal.invoke_faulty_distributed_command();
\set ON_ERROR_STOP 1
\c :DATA_NODE_1
SELECT * from disttable2;
\c :DATA_NODE_2
SELECT * from disttable2;
-- Test connection session identity
\c :TEST_DBNAME :ROLE_SUPERUSER
\unset ECHO
\o /dev/null
\ir include/remote_exec.sql
\o
\set ECHO all
-- Register_is_access_node_session_on_data_node() function and test that it returns false for
-- connections openned by test suite. This simualates behaviour expected
-- with a client connections.
CREATE OR REPLACE FUNCTION is_access_node_session_on_data_node()
RETURNS BOOL
AS :TSL_MODULE_PATHNAME, 'ts_test_dist_util_is_access_node_session_on_data_node' LANGUAGE C;
\c :DATA_NODE_1
CREATE OR REPLACE FUNCTION is_access_node_session_on_data_node()
RETURNS BOOL
AS :TSL_MODULE_PATHNAME, 'ts_test_dist_util_is_access_node_session_on_data_node' LANGUAGE C;
SELECT is_access_node_session_on_data_node();
\c :DATA_NODE_2
CREATE OR REPLACE FUNCTION is_access_node_session_on_data_node()
RETURNS BOOL
AS :TSL_MODULE_PATHNAME, 'ts_test_dist_util_is_access_node_session_on_data_node' LANGUAGE C;
SELECT is_access_node_session_on_data_node();
\c :DATA_NODE_3
CREATE OR REPLACE FUNCTION is_access_node_session_on_data_node()
RETURNS BOOL
AS :TSL_MODULE_PATHNAME, 'ts_test_dist_util_is_access_node_session_on_data_node' LANGUAGE C;
SELECT is_access_node_session_on_data_node();
\c :TEST_DBNAME :ROLE_SUPERUSER
SET ROLE :ROLE_1;
SELECT is_access_node_session_on_data_node();
-- Ensure peer dist id is already set and can be set only once
\set ON_ERROR_STOP 0
SELECT * FROM test.remote_exec(ARRAY[:'DATA_NODE_1'], $$ SELECT * FROM _timescaledb_internal.set_peer_dist_id('77348176-09da-4a80-bc78-e31bdf5e63ec'); $$);
\set ON_ERROR_STOP 1
-- Repeat is_access_node_session_on_data_node() test again, but this time using connections openned from
-- access node to data nodes. Must return true.
SELECT * FROM test.remote_exec(NULL, $$ SELECT is_access_node_session_on_data_node(); $$);
-- Test distributed_exec()
-- Test calling distributed exec via direct function call
RESET ROLE;
CREATE OR REPLACE PROCEDURE distributed_exec_direct_function_call(
query TEXT,
node_list name[] = NULL,
transactional BOOLEAN = TRUE)
AS :TSL_MODULE_PATHNAME, 'ts_test_direct_function_call' LANGUAGE C;
SET ROLE :ROLE_1;
-- Invalid input
\set ON_ERROR_STOP 0
\set VERBOSITY default
BEGIN;
-- Not allowed in transcation block if transactional=false
CALL distributed_exec_direct_function_call('CREATE TABLE dist_test(id int)', NULL, false);
ROLLBACK;
-- multi-dimensional array of data nodes
CALL distributed_exec('CREATE TABLE dist_test(id int)', '{{db_data_node_1}}');
-- Specified, but empty data node array
CALL distributed_exec('CREATE TABLE dist_test(id int)', '{}');
-- Specified, but contains null values
CALL distributed_exec('CREATE TABLE dist_test(id int)', '{db_data_node_1, NULL}');
-- Specified, but not a data node
CALL distributed_exec('CREATE TABLE dist_test(id int)', '{db_data_node_1928}');
\set VERBOSITY terse
\set ON_ERROR_STOP 1
-- Make sure dist session is properly set
CALL distributed_exec('DO $$ BEGIN ASSERT(SELECT is_access_node_session_on_data_node()) = true; END; $$;');
-- Test creating and dropping a table
CALL distributed_exec('CREATE TABLE dist_test (id int)');
CALL distributed_exec('INSERT INTO dist_test values (7)');
-- Test INSERTING data using empty array of data nodes (same behavior as not specifying).
SELECT * FROM test.remote_exec(NULL, $$ SELECT * from dist_test; $$);
CALL distributed_exec('DROP TABLE dist_test');
\set ON_ERROR_STOP 0
CALL distributed_exec('INSERT INTO dist_test VALUES (8)', ARRAY[:'DATA_NODE_1']);
\set ON_ERROR_STOP 1
-- Test creating and dropping a role
CREATE ROLE dist_test_role;
-- Expect this to be an error, since data nodes are created on the same instance
\set ON_ERROR_STOP 0
SELECT test.execute_sql_and_filter_data_node_name_on_error($$
CALL distributed_exec('CREATE ROLE dist_test_role');
$$);
\set ON_ERROR_STOP 1
SELECT * FROM test.remote_exec(NULL, $$ SELECT true from pg_catalog.pg_roles WHERE rolname = 'dist_test_role'; $$);
DROP ROLE DIST_TEST_ROLE;
\set ON_ERROR_STOP 0
SELECT test.execute_sql_and_filter_data_node_name_on_error($$
CALL distributed_exec('DROP ROLE dist_test_role');
$$);
\set ON_ERROR_STOP 1
-- Do not allow to run distributed_exec() on a data nodes
\c :DATA_NODE_1
\set ON_ERROR_STOP 0
CALL distributed_exec('SELECT 1');
\set ON_ERROR_STOP 1
\c :TEST_DBNAME :ROLE_SUPERUSER
-- Test health check function output on access node
SELECT * FROM _timescaledb_internal.health() ORDER BY 1 NULLS FIRST;
SELECT * FROM delete_data_node(:'DATA_NODE_1');
SELECT * FROM delete_data_node(:'DATA_NODE_2');
SELECT * FROM delete_data_node(:'DATA_NODE_3');
-- Test health check when no longer an access node (no data nodes)
SELECT * FROM _timescaledb_internal.health() ORDER BY 1 NULLS FIRST;
DROP DATABASE :DATA_NODE_1;
DROP DATABASE :DATA_NODE_2;
DROP DATABASE :DATA_NODE_3;
\set ON_ERROR_STOP 0
-- Calling distributed_exec without data nodes should fail
CALL distributed_exec('SELECT 1');
CALL distributed_exec('SELECT 1', '{data_node_1}');
\set ON_ERROR_STOP 1
-- Test TS execution on non-TSDB server
CREATE EXTENSION postgres_fdw;
CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'foo', dbname 'foodb', port '5432');
\set ON_ERROR_STOP 0
SELECT * FROM test.remote_exec('{myserver}', $$ SELECT 1; $$);
\set ON_ERROR_STOP 1
DROP SERVER myserver;
DROP EXTENSION postgres_fdw;
-- Test that transactional behaviour is the default and that it can be
-- disabled.
--
-- In this case, we only execute it on one data node since we are
-- creating a database and multiple creations of the database would
-- clash when executed on the same instace.
--
-- We prefix the database names with the test file to be able to
-- parallelize the test. Not possible right now because there are
-- other databases above that prevents this.
\c :TEST_DBNAME :ROLE_SUPERUSER
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('dist_commands_1', host => 'localhost', database => :'DATA_NODE_1');
SELECT node_name, database, node_created, database_created, extension_created FROM add_data_node('dist_commands_2', host => 'localhost', database => :'DATA_NODE_2');
GRANT USAGE ON FOREIGN SERVER dist_commands_1, dist_commands_2 TO PUBLIC;
\set ON_ERROR_STOP 0
CALL distributed_exec('CREATE DATABASE dist_commands_magic',
node_list => '{dist_commands_1}');
\set ON_ERROR_STOP 1
CALL distributed_exec('CREATE DATABASE dist_commands_magic',
node_list => '{dist_commands_1}', transactional => FALSE);
DROP DATABASE dist_commands_magic;
-- Test that distributed_exec honor the 2PC behaviour when starting a
-- transaction locally. It should also give an error if attempting to
-- execute non-transactionally inside a local transaction.
-- To test that distributed_exec honors transactions, we create a
-- table on both data nodes, and then tweak one of the tables so that
-- we get a duplicate key when updating the table on both data
-- nodes. This should then abort the transaction on all data nodes.
\c :TEST_DBNAME :ROLE_1
CALL distributed_exec($$
CREATE TABLE my_table (key INT, value TEXT, PRIMARY KEY (key));
$$);
\c :DATA_NODE_1
INSERT INTO my_table VALUES (1, 'foo');
\c :TEST_DBNAME :ROLE_1
\set ON_ERROR_STOP 0
BEGIN;
CALL distributed_exec($$ INSERT INTO my_table VALUES (1, 'bar') $$);
COMMIT;
\set ON_ERROR_STOP 1
-- No changes should be there
SELECT * FROM test.remote_exec(NULL, $$ SELECT * FROM my_table; $$);
-- This should work.
BEGIN;
CALL distributed_exec($$ INSERT INTO my_table VALUES (2, 'bar'); $$);
COMMIT;
-- We should see changes
SELECT * FROM test.remote_exec(NULL, $$ SELECT * FROM my_table; $$);
-- This should fail since we are inside a transaction and asking for
-- transactional execution on the remote nodes. Non-transactional
-- execution should be outside transactions.
\set ON_ERROR_STOP 0
BEGIN;
CALL distributed_exec(
$$ INSERT INTO my_table VALUES (3, 'baz') $$,
transactional => FALSE
);
COMMIT;
\set ON_ERROR_STOP 1
-- We should see no changes
SELECT * FROM test.remote_exec(NULL, $$ SELECT * FROM my_table; $$);
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER
DROP DATABASE :DATA_NODE_1;
DROP DATABASE :DATA_NODE_2;