Move enterprise features to community

This patch removes enterprise license support and moves
move_chunk() function under community license (TSL).

Licensing validation code been reworked and simplified.
Previously used timescaledb.license_key guc been renamed to
timescaledb.license.

This change also makes testing code more strict against
used license. Apache test suite now can test only apache-licensed
functions.

Fixes #2359
This commit is contained in:
Dmitry Simonenko 2020-09-30 14:43:26 +03:00
parent 66c63476e5
commit a51aa6d04b
61 changed files with 370 additions and 1617 deletions

View File

@ -183,22 +183,17 @@ test_script:
#right now we only run timescale regression tests, others will be set up later
docker exec -e IGNORES="bgw_db_scheduler chunk_utils loader" -e SKIPS="bgw_db_scheduler" -e TEST_TABLESPACE1_PATH="C:\Users\$env:UserName\Documents\tablespace1\" -e TEST_TABLESPACE2_PATH="C:\Users\$env:UserName\Documents\tablespace2\" -e TEST_SPINWAIT_ITERS=10000 -e USER=postgres -e PG_REGRESS_OPTS="--bindir=/usr/local/bin/" -it pgregress /bin/bash -c "cd /timescaledb/build && make regresschecklocal"
docker exec -e IGNORES="chunk_utils loader license" -e SKIPS="license" -e TEST_TABLESPACE1_PATH="C:\Users\$env:UserName\Documents\tablespace1\" -e TEST_TABLESPACE2_PATH="C:\Users\$env:UserName\Documents\tablespace2\" -e TEST_SPINWAIT_ITERS=10000 -e USER=postgres -e PG_REGRESS_OPTS="--bindir=/usr/local/bin/" -it pgregress /bin/bash -c "cd /timescaledb/build && make regresschecklocal"
$TESTS1 = $?
# Normally we use different config files for apache and enterprise tests, but Windows was having problems with that
# Thus, just append the license key to the regular config file instead
Add-Content "C:\Program Files\postgresql\12\data\postgresql.conf" "timescaledb.license_key = 'E1eyJlbmRfdGltZSI6IjIwMTgtMTAtMDEgKzAwMDAiLCAic3RhcnRfdGltZSI6IjIwMTgtMDktMDEgKzAwMDAiLCAiaWQiOiI0OTBGQjI2MC1BMjkyLTRBRDktOUFBMi0wMzYwODM1NzkxQjgiLCAia2luZCI6InRyaWFsIn0K'"
Restart-Service postgresql-x64-12
# Windows doesn't support SIGTERM used by the node
# killer. Therefore, we need to ignore the results of the
# remote_connection and remote_txn tests.
docker exec -e IGNORES="compression_algos continuous_aggs_bgw remote_connection remote_txn" -e TEST_TABLESPACE1_PATH="C:\Users\$env:UserName\Documents\tablespace1\" -e TEST_TABLESPACE2_PATH="C:\Users\$env:UserName\Documents\tablespace2\" -e TEST_SPINWAIT_ITERS=10000 -e USER=postgres -e PG_REGRESS_OPTS="--bindir=/usr/local/bin/" -it pgregress /bin/bash -c "cd /timescaledb/build && make regresschecklocal-t"
docker exec -e IGNORES="bgw_db_scheduler compression_algos continuous_aggs_bgw remote_connection remote_txn" -e SKIPS="bgw_db_scheduler" -e TEST_TABLESPACE1_PATH="C:\Users\$env:UserName\Documents\tablespace1\" -e TEST_TABLESPACE2_PATH="C:\Users\$env:UserName\Documents\tablespace2\" -e TEST_SPINWAIT_ITERS=10000 -e USER=postgres -e PG_REGRESS_OPTS="--bindir=/usr/local/bin/" -it pgregress /bin/bash -c "cd /timescaledb/build && make regresschecklocal-t"
if( -not $? -or -not $TESTS1 ) { exit 1 }

View File

@ -25,7 +25,6 @@ set(SOURCE_FILES
hypertable.sql
chunk.sql
ddl_internal.sql
edition.sql
util_time.sql
util_internal_table_ddl.sql
chunk_constraint.sql

View File

@ -1,37 +0,0 @@
-- 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.
CREATE OR REPLACE FUNCTION _timescaledb_internal.enterprise_enabled() RETURNS BOOLEAN
AS '@MODULE_PATHNAME@', 'ts_enterprise_enabled' LANGUAGE C;
CREATE OR REPLACE FUNCTION _timescaledb_internal.current_license_key() RETURNS TEXT
AS '@MODULE_PATHNAME@', 'ts_current_license_key' LANGUAGE C;
CREATE OR REPLACE FUNCTION _timescaledb_internal.tsl_loaded() RETURNS BOOLEAN
AS '@MODULE_PATHNAME@', 'ts_tsl_loaded' LANGUAGE C;
CREATE OR REPLACE FUNCTION _timescaledb_internal.license_expiration_time() RETURNS TIMESTAMPTZ
AS '@MODULE_PATHNAME@', 'ts_license_expiration_time' LANGUAGE C;
CREATE OR REPLACE FUNCTION _timescaledb_internal.print_license_expiration_info() RETURNS VOID
AS '@MODULE_PATHNAME@', 'ts_print_tsl_license_expiration_info' LANGUAGE C;
CREATE OR REPLACE FUNCTION _timescaledb_internal.license_edition() RETURNS TEXT
AS '@MODULE_PATHNAME@', 'ts_license_edition' LANGUAGE C;
CREATE OR REPLACE FUNCTION _timescaledb_internal.current_db_set_license_key(new_key TEXT) RETURNS TEXT AS
$BODY$
DECLARE
db text;
BEGIN
SELECT current_database() INTO db;
EXECUTE format('ALTER DATABASE %I SET timescaledb.license_key = %L', db, new_key);
EXECUTE format('SET SESSION timescaledb.license_key = %L', new_key);
PERFORM _timescaledb_internal.restart_background_workers();
RETURN new_key;
END
$BODY$
LANGUAGE PLPGSQL;

View File

@ -4,19 +4,8 @@
DO language plpgsql $$
DECLARE
end_time TIMESTAMPTZ;
expiration_time_string TEXT;
telemetry_string TEXT;
BEGIN
end_time := _timescaledb_internal.license_expiration_time();
IF end_time IS NOT NULL AND isfinite(end_time)
THEN
expiration_time_string = format(E'\nYour license expires on %s\n', end_time);
ELSE
expiration_time_string = '';
END IF;
IF current_setting('timescaledb.telemetry_level') = 'off'
THEN
telemetry_string = E'Note: Please enable telemetry to help us improve our product by running: ALTER DATABASE "' || current_database() || E'" SET timescaledb.telemetry_level = ''basic'';';
@ -24,7 +13,7 @@ BEGIN
telemetry_string = E'Note: TimescaleDB collects anonymous reports to better understand and assist our users.\nFor more information and how to disable, please see our docs https://docs.timescaledb.com/using-timescaledb/telemetry.';
END IF;
RAISE WARNING E'%\n%\n%',
RAISE WARNING E'%\n%\n',
E'\nWELCOME TO\n' ||
E' _____ _ _ ____________ \n' ||
E'|_ _(_) | | | _ \\ ___ \\ \n' ||
@ -39,18 +28,6 @@ BEGIN
E' 1. Getting started: https://docs.timescale.com/getting-started\n' ||
E' 2. API reference documentation: https://docs.timescale.com/api\n' ||
E' 3. How TimescaleDB is designed: https://docs.timescale.com/introduction/architecture\n',
telemetry_string,
expiration_time_string;
IF now() > end_time
THEN
RAISE WARNING E'%\n', format('Your license expired on %s', end_time);
ELSIF now() + INTERVAL '1 week' >= end_time
THEN
RAISE WARNING E'%\n', format('Your license will expire on %s', end_time);
END IF;
telemetry_string;
END;
$$;
select _timescaledb_internal.print_license_expiration_info();

View File

@ -13,6 +13,7 @@ DROP FUNCTION IF EXISTS _timescaledb_internal.range_value_to_pretty;
DROP FUNCTION IF EXISTS hypertable_approximate_row_count;
DROP VIEW IF EXISTS timescaledb_information.compressed_chunk_stats;
DROP VIEW IF EXISTS timescaledb_information.compressed_hypertable_stats;
DROP VIEW IF EXISTS timescaledb_information.license;
-- Add new function definitions, columns and tables for distributed hypertables
DROP FUNCTION IF EXISTS create_hypertable(regclass,name,name,integer,name,name,anyelement,boolean,boolean,regproc,boolean,text,regproc,regproc);
@ -23,6 +24,12 @@ DROP FUNCTION IF EXISTS show_chunks;
DROP FUNCTION IF EXISTS add_compress_chunks_policy;
DROP FUNCTION IF EXISTS remove_compress_chunks_policy;
DROP FUNCTION IF EXISTS alter_job_schedule;
DROP FUNCTION IF EXISTS _timescaledb_internal.enterprise_enabled;
DROP FUNCTION IF EXISTS _timescaledb_internal.current_license_key;
DROP FUNCTION IF EXISTS _timescaledb_internal.license_expiration_time;
DROP FUNCTION IF EXISTS _timescaledb_internal.print_license_expiration_info;
DROP FUNCTION IF EXISTS _timescaledb_internal.license_edition;
DROP FUNCTION IF EXISTS _timescaledb_internal.current_db_set_license_key;
DROP VIEW IF EXISTS timescaledb_information.policy_stats;
DROP VIEW IF EXISTS timescaledb_information.drop_chunks_policies;

View File

@ -11,3 +11,6 @@ CREATE OR REPLACE FUNCTION _timescaledb_internal.get_os_info()
CREATE OR REPLACE FUNCTION get_telemetry_report(always_display_report boolean DEFAULT false) RETURNS TEXT
AS '@MODULE_PATHNAME@', 'ts_get_telemetry_report' LANGUAGE C STABLE PARALLEL SAFE;
CREATE OR REPLACE FUNCTION _timescaledb_internal.tsl_loaded() RETURNS BOOLEAN
AS '@MODULE_PATHNAME@', 'ts_tsl_loaded' LANGUAGE C;

View File

@ -42,11 +42,6 @@ CREATE OR REPLACE VIEW timescaledb_information.hypertables AS
WHERE ht.compressed is false --> no internal compression tables
AND ca.mat_hypertable_id IS NULL;
CREATE OR REPLACE VIEW timescaledb_information.license AS
SELECT _timescaledb_internal.license_edition() as edition,
_timescaledb_internal.license_expiration_time() <= now() AS expired,
_timescaledb_internal.license_expiration_time() AS expiration_time;
CREATE OR REPLACE VIEW timescaledb_information.job_stats as
SELECT format('%1$I.%2$I', ht.schema_name, ht.table_name)::regclass as hypertable, j.id AS job_id,
js.last_start as last_run_started_at,

View File

@ -127,21 +127,8 @@ error_no_default_fn_community(void)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("functionality not supported under the current license \"%s\", license",
ts_guc_license_key),
errhint(
"Upgrade to a Timescale-licensed binary to access this free community feature")));
}
static void
error_no_default_fn_enterprise(void)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("functionality not supported under the current license \"%s\", license",
ts_guc_license_key),
errhint("Request a trial license to try this feature for free or contact us for more "
"information at https://www.timescale.com/pricing")));
errmsg("functionality not supported under the current \"%s\" license", ts_guc_license),
errhint("Upgrade your license to 'timescale' to use this free community feature.")));
}
static bool
@ -151,26 +138,6 @@ error_no_default_fn_bool_void_community(void)
pg_unreachable();
}
static bool
error_no_default_fn_bool_void_enterprise(void)
{
error_no_default_fn_enterprise();
pg_unreachable();
}
static void
tsl_license_on_assign_default_fn(const char *newval, const void *license)
{
error_no_default_fn_community();
}
static TimestampTz
license_end_time_default_fn(void)
{
error_no_default_fn_community();
pg_unreachable();
}
static void
add_tsl_telemetry_info_default(JsonbParseState **parse_state)
{
@ -197,11 +164,10 @@ error_no_default_fn_pg_community(PG_FUNCTION_ARGS)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function \"%s\" is not supported under the current license \"%s\"",
errmsg("function \"%s\" is not supported under the current \"%s\" license",
get_func_name(fcinfo->flinfo->fn_oid),
ts_guc_license_key),
errhint(
"Upgrade to a Timescale-licensed binary to access this free community feature")));
ts_guc_license),
errhint("Upgrade your license to 'timescale' to use this free community feature.")));
pg_unreachable();
}
@ -226,19 +192,6 @@ cache_syscache_invalidate_default(Datum arg, int cacheid, uint32 hashvalue)
/* The default is a no-op */
}
static Datum
error_no_default_fn_pg_enterprise(PG_FUNCTION_ARGS)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function \"%s\" is not supported under the current license \"%s\"",
get_func_name(fcinfo->flinfo->fn_oid),
ts_guc_license_key),
errhint("Request a trial license to try this feature for free or contact us for more "
"information at https://www.timescale.com/pricing")));
pg_unreachable();
}
static DDLResult
process_cagg_viewstmt_default(Node *stmt, const char *query_string, void *pstmt,
WithClauseResult *with_clause_options)
@ -308,18 +261,20 @@ func_call_on_data_nodes_default(FunctionCallInfo finfo, List *data_node_oids)
pg_unreachable();
}
TS_FUNCTION_INFO_V1(ts_tsl_loaded);
PGDLLEXPORT Datum
ts_tsl_loaded(PG_FUNCTION_ARGS)
{
PG_RETURN_BOOL(ts_cm_functions != &ts_cm_functions_default);
}
/*
* Define cross-module functions' default values:
* If the submodule isn't activated, using one of the cm functions will throw an
* exception.
*/
TSDLLEXPORT CrossModuleFunctions ts_cm_functions_default = {
.tsl_license_on_assign = tsl_license_on_assign_default_fn,
.enterprise_enabled_internal = error_no_default_fn_bool_void_enterprise,
.check_tsl_loaded = error_no_default_fn_bool_void_community,
.license_end_time = license_end_time_default_fn,
.print_tsl_license_expiration_info_hook = NULL,
.module_shutdown_hook = NULL,
.add_tsl_telemetry_info = add_tsl_telemetry_info_default,
.create_upper_paths_hook = NULL,
.set_rel_pathlist_dml = NULL,
@ -358,7 +313,7 @@ TSDLLEXPORT CrossModuleFunctions ts_cm_functions_default = {
.job_run = error_no_default_fn_pg_community,
.job_execute = job_execute_default_fn,
.move_chunk = error_no_default_fn_pg_enterprise,
.move_chunk = error_no_default_fn_pg_community,
.reorder_chunk = error_no_default_fn_pg_community,
.partialize_agg = error_no_default_fn_pg_community,

View File

@ -38,12 +38,6 @@ typedef struct CopyChunkState CopyChunkState;
typedef struct CrossModuleFunctions
{
void (*tsl_license_on_assign)(const char *newval, const void *license);
bool (*enterprise_enabled_internal)(void);
bool (*check_tsl_loaded)(void);
TimestampTz (*license_end_time)(void);
void (*print_tsl_license_expiration_info_hook)(void);
void (*module_shutdown_hook)(void);
void (*add_tsl_telemetry_info)(JsonbParseState **parse_state);
PGFunction policy_compression_add;

View File

@ -14,6 +14,8 @@
#define TSL_LIBRARY_NAME "timescaledb-tsl"
#define TS_LIBDIR "$libdir/"
#define EXTENSION_SO TS_LIBDIR "" EXTENSION_NAME
#define EXTENSION_TSL_SO TS_LIBDIR TSL_LIBRARY_NAME "-" TIMESCALEDB_VERSION_MOD
#define MAX_VERSION_LEN (NAMEDATALEN + 1)
#define MAX_SO_NAME_LEN \
(8 + NAMEDATALEN + 1 + MAX_VERSION_LEN) /* "$libdir/"+extname+"-"+version \

View File

@ -54,7 +54,7 @@ int ts_guc_max_open_chunks_per_insert = 10;
int ts_guc_max_cached_chunks_per_hypertable = 10;
int ts_guc_telemetry_level = TELEMETRY_DEFAULT;
TSDLLEXPORT char *ts_guc_license_key = TS_DEFAULT_LICENSE;
TSDLLEXPORT char *ts_guc_license = TS_LICENSE_DEFAULT;
char *ts_last_tune_time = NULL;
char *ts_last_tune_version = NULL;
char *ts_telemetry_cloud = NULL;
@ -360,15 +360,15 @@ _guc_init(void)
NULL,
NULL);
DefineCustomStringVariable(/* name= */ "timescaledb.license_key",
/* short_dec= */ "TimescaleDB license key",
DefineCustomStringVariable(/* name= */ "timescaledb.license",
/* short_dec= */ "TimescaleDB license type",
/* long_dec= */ "Determines which features are enabled",
/* valueAddr= */ &ts_guc_license_key,
/* bootValue= */ TS_DEFAULT_LICENSE,
/* valueAddr= */ &ts_guc_license,
/* bootValue= */ TS_LICENSE_DEFAULT,
/* context= */ PGC_SUSET,
/* flags= */ GUC_SUPERUSER_ONLY,
/* check_hook= */ ts_license_update_check,
/* assign_hook= */ ts_license_on_assign,
/* flags= */ 0,
/* check_hook= */ ts_license_guc_check_hook,
/* assign_hook= */ ts_license_guc_assign_hook,
/* show_hook= */ NULL);
DefineCustomStringVariable(/* name= */ "timescaledb.last_tuned",

View File

@ -26,7 +26,7 @@ extern bool ts_guc_restoring;
extern int ts_guc_max_open_chunks_per_insert;
extern int ts_guc_max_cached_chunks_per_hypertable;
extern int ts_guc_telemetry_level;
extern TSDLLEXPORT char *ts_guc_license_key;
extern TSDLLEXPORT char *ts_guc_license;
extern char *ts_last_tune_time;
extern char *ts_last_tune_version;
extern char *ts_telemetry_cloud;

View File

@ -15,90 +15,84 @@
#include "license_guc.h"
#include "cross_module_fn.h"
TS_FUNCTION_INFO_V1(ts_enterprise_enabled);
TS_FUNCTION_INFO_V1(ts_current_license_key);
TS_FUNCTION_INFO_V1(ts_tsl_loaded);
TS_FUNCTION_INFO_V1(ts_print_tsl_license_expiration_info);
TS_FUNCTION_INFO_V1(ts_license_expiration_time);
TS_FUNCTION_INFO_V1(ts_license_edition);
TS_FUNCTION_INFO_V1(ts_allow_downgrade_to_apache);
static bool downgrade_to_apache_enabled = false;
static void *tsl_handle = NULL;
static PGFunction tsl_validate_license_fn = NULL;
static PGFunction tsl_startup_fn = NULL;
static bool can_load = false;
static bool load_enabled = false;
static GucSource load_source = PGC_S_DEFAULT;
#define TS_LICENSE_APACHE_ONLY_PRINT_TEXT "apache"
#define TS_LICENSE_COMMUNITY_PRINT_TEXT "community"
#define TS_LICENSE_ENTERPRISE_PRINT_TEXT "enterprise"
static void *tsl_handle = NULL;
static PGFunction tsl_init_fn = NULL;
/*
* License Functions
* License Functions.
*
* License validation is performed via guc update-hooks.
* In this file we check if the type of license supplied warrants loading an
* additional module. It loading one is warranted, that module performs the
* final validation that the license is correct.
* additional module.
*
*/
static bool load_tsl();
static bool
current_license_can_downgrade_to_apache(void)
{
if (downgrade_to_apache_enabled)
return true;
return (ts_guc_license_key == NULL || TS_CURRENT_LICENSE_IS_APACHE_ONLY()) &&
tsl_handle == NULL;
}
/*
* GUC checks work in two parts:
*
* 1. In the check function, all validation of the new value is performed
* and any auxiliary state is setup but _not_ installed. Postgres is
* allowed to call the check function in cases where the newval will never
* be used, so any effects of this function must be confined to `extra` or
* otherwise be un-observable. Further, this function is _not_ allowed to
* throw exceptions.
* and any auxiliary state is setup but not installed. This function
* is not allowed to throw exceptions.
*
* 2. In the assign function all user-visible state is installed. This
* function *MUST NOT FAIL* as it can be called from such places as
* transaction commitment, and will cause database restarts if it fails.
*
* Therefore our license validation also works in two parts, corresponding to
* Therefore license validation also works in two parts, corresponding to
* check and assign:
* 1. In the first stage we check the license kind, load the submodule into
* memory if needed (but don't link any of the cross-module functions yet)
* and, if it's an enterprise license, validate the license key.
* 2. In the second stage we link all of the cross-module functions.
*
* The first stage will fail if the provided license key is invalid, or it's
* trying to downgrade from an edition that uses the submodule
* (Community and Enterprise) to one that does not (ApacheOnly). Currently only
* upgrading is allowed within a session; downgrading requires starting a new
* session.
* 1. In the first stage we check the license type, load the submodule into
* memory if needed (but don't link any of the cross-module functions yet).
*
* 2. In the second stage we link all of the cross-module functions and init
* tsl module.
*
* In order for restoring libraries to work (e.g. in parallel workers), loading
* the submodule must happen strictly after the main timescaledb module is
* loaded. In order to ensure that the initial value doesn't break this, we
* disable loading submodules until the post_load_init.
*
* No license change from user session is allowed. License can be changed only
* if it is set from server configuration file or the server command line.
*/
typedef enum
{
LICENSE_UNDEF,
LICENSE_APACHE,
LICENSE_TIMESCALE
} LicenseType;
static LicenseType
license_type_of(const char *string)
{
if (string == NULL)
return LICENSE_UNDEF;
if (strcmp(string, TS_LICENSE_TIMESCALE) == 0)
return LICENSE_TIMESCALE;
if (strcmp(string, TS_LICENSE_APACHE) == 0)
return LICENSE_APACHE;
return LICENSE_UNDEF;
}
bool
ts_license_is_apache(void)
{
return license_type_of(ts_guc_license) == LICENSE_APACHE;
}
TSDLLEXPORT void
ts_license_enable_module_loading(void)
{
int result;
if (can_load)
if (load_enabled)
return;
can_load = true;
/* re-set the license key to actually load the submodule if needed */
result = set_config_option("timescaledb.license_key",
ts_guc_license_key,
load_enabled = true;
/* re-set the license to actually load the submodule if needed */
result = set_config_option("timescaledb.license",
ts_guc_license,
PGC_SUSET,
load_source,
GUC_ACTION_SET,
@ -107,279 +101,100 @@ ts_license_enable_module_loading(void)
false);
if (result <= 0)
elog(ERROR, "invalid value for timescaledb.license_key '%s'", ts_guc_license_key);
elog(ERROR, "invalid value for timescaledb.license: \"%s\"", ts_guc_license);
}
/*
* `ts_license_update_check`
* Used to validate license keys in preparation for `ts_license_on_assign`
* TSL module load function.
*
* Load the module, but do not start it. Set tsl_handle and
* tsl_init_fn module init function pointer (tsl/src/init.c).
*
* This function is idempotent, and will not reload the module
* if called multiple times.
*/
static bool
tsl_module_load(void)
{
void *function;
void *handle;
if (tsl_handle != NULL)
return true;
function = load_external_function(EXTENSION_TSL_SO, "ts_module_init", false, &handle);
if (function == NULL || handle == NULL)
return false;
tsl_init_fn = function;
tsl_handle = handle;
return true;
}
static void
tsl_module_init(void)
{
Assert(tsl_handle != NULL);
Assert(tsl_init_fn != NULL);
DirectFunctionCall1(tsl_init_fn, CharGetDatum(0));
}
/*
* Check hook function set by license guc.
*
* Used to validate license string before the assign hook
* ts_license_guc_assign_hook() call.
*/
bool
ts_license_update_check(char **newval, void **extra, GucSource source)
ts_license_guc_check_hook(char **newval, void **extra, GucSource source)
{
Datum module_can_start;
bool try_to_load_tsl = true;
LicenseType type = license_type_of(*newval);
if (*newval == NULL)
return false;
if (!TS_LICENSE_TYPE_IS_VALID(*newval))
return false;
/*
* we can shutdown the submodule if it was never loaded, or it has a valid
* shutdown function.
*/
if (TS_LICENSE_IS_APACHE_ONLY(*newval))
/* Allow setting a license only if is is set from postgresql.conf
* or the server command line */
switch (type)
{
if (!current_license_can_downgrade_to_apache())
{
GUC_check_errdetail("Cannot downgrade a running session to Apache Only.");
GUC_check_errhint("change the license in the configuration file");
case LICENSE_APACHE:
case LICENSE_TIMESCALE:
if (source == PGC_S_FILE || source == PGC_S_ARGV || source == PGC_S_DEFAULT)
break;
GUC_check_errdetail("Cannot change a license in a running session.");
GUC_check_errhint(
"Change the license in the configuration file or server command line.");
return false;
case LICENSE_UNDEF:
GUC_check_errdetail("Unrecognized license type.");
GUC_check_errhint("Supported license types are 'timescale' or 'apache'.");
return false;
}
try_to_load_tsl = false;
}
if (!can_load)
/* If loading is delayed, save the GucSource for later retry
* in the ts_license_enable_module_loading() */
if (!load_enabled)
{
load_source = source;
return true;
}
if (!try_to_load_tsl)
return true;
if (!load_tsl())
if (type == LICENSE_TIMESCALE && !tsl_module_load())
{
GUC_check_errdetail("Could not find additional timescaledb module");
GUC_check_errhint("check that %s-%s is available",
TSL_LIBRARY_NAME,
TIMESCALEDB_VERSION_MOD);
GUC_check_errdetail("Could not find TSL timescaledb module.");
GUC_check_errhint("Check that \"%s\" is available.", EXTENSION_TSL_SO);
return false;
}
Assert(tsl_handle != NULL);
Assert(tsl_validate_license_fn != NULL);
Assert(tsl_startup_fn != NULL);
#ifdef WIN32
/*
* freeing the guc extra causes heap corruption on windows so instead we
* re-parse in the assign hook
*/
extra = NULL;
#endif
module_can_start = DirectFunctionCall2(tsl_validate_license_fn,
CStringGetDatum(*newval),
PointerGetDatum(extra));
return DatumGetBool(module_can_start);
return true;
}
#ifdef WIN32
static void *
revalidate_license(const char *newval)
{
void *extra = NULL;
void **extra_p = &extra;
Assert(extra == NULL);
/*
* Due to windows issues we cannot use the extra parameter. Instead
* re-call the validation function, if we reach this point the license
* must be valid so the function cannot fail cannot fail.
*/
DirectFunctionCall2(tsl_validate_license_fn, CStringGetDatum(newval), PointerGetDatum(extra_p));
return extra;
}
#endif
/*
* `ts_license_on_assign`
* Links the cross-module function pointers.
* Assign hook function set by license guc, executed right after
* ts_license_guc_check_hook() hook call.
*
* Executes tsl module init function (tsl/src/init.c) which sets the
* cross-module function pointers.
*/
void
ts_license_on_assign(const char *newval, void *extra)
ts_license_guc_assign_hook(const char *newval, void *extra)
{
if (!can_load)
return;
Assert(newval != NULL);
Assert(TS_LICENSE_TYPE_IS_VALID(newval));
if (TS_LICENSE_IS_APACHE_ONLY(newval))
{
Assert(current_license_can_downgrade_to_apache());
Assert(extra == NULL);
if (ts_cm_functions->module_shutdown_hook != NULL)
ts_cm_functions->module_shutdown_hook();
return;
}
Assert(tsl_handle != NULL);
Assert(tsl_startup_fn != NULL);
DirectFunctionCall1(tsl_startup_fn, CharGetDatum(0));
#ifdef WIN32
Assert(extra == NULL);
extra = revalidate_license(newval);
#endif
Assert(extra != NULL);
ts_cm_functions->tsl_license_on_assign(newval, extra);
}
/* Module Functions */
/*
* Load the module, but do not start it.
* If this function succeeds, `tsl_handle`, and
* `tsl_startup_fn` will be set, but no `TS_FN` functions will
* change. `tsl_startup_fn` can be used to enable the submodule
* versions of `TS_FN`s.
*
* This function is idempotent, and will _not_ reload the module
* if called multiple times.
*
* returns:
* a function pointer to `tsl_license_update_check` or NULL if the
* loading fails
*
*/
static bool load_tsl(void);
static bool
load_tsl(void)
{
char soname[MAX_SO_NAME_LEN] = { 0 };
if (tsl_handle != NULL)
{
Assert(tsl_startup_fn != NULL);
/*
* We don't want to reload the submodule if it was loaded successfully
* in the past because that may relocate symbols we're using. Instead
* skip to loading the validation function.
*/
goto get_validation_function;
}
snprintf(soname, MAX_SO_NAME_LEN, TS_LIBDIR "%s-%s", TSL_LIBRARY_NAME, TIMESCALEDB_VERSION_MOD);
tsl_startup_fn = load_external_function(
/* filename= */ soname,
/* funcname= */ "ts_module_init",
/* signalNotFound= */ false,
/* filehandle= */ &tsl_handle);
if (tsl_handle == NULL || tsl_startup_fn == NULL)
goto loading_failed;
get_validation_function:
tsl_validate_license_fn = lookup_external_function(tsl_handle, "tsl_license_update_check");
if (tsl_validate_license_fn == NULL)
goto loading_failed;
return true;
/*
* We want this function to be atomic, either all three relevant values are set
* or none of them are: If we fail to find one of the values set the two static
* ones back to NULL. (If they were non-NULL to start with that means the
* function must have been called successfully in the past, so the lookups
* should still succeed)
*/
loading_failed:
tsl_handle = NULL;
tsl_startup_fn = NULL;
tsl_validate_license_fn = NULL;
return false;
}
/* SQL functions */
PGDLLEXPORT Datum
ts_tsl_loaded(PG_FUNCTION_ARGS)
{
if (TS_CURRENT_LICENSE_IS_APACHE_ONLY())
PG_RETURN_BOOL(false);
PG_RETURN_BOOL(ts_cm_functions->check_tsl_loaded());
}
PGDLLEXPORT Datum
ts_enterprise_enabled(PG_FUNCTION_ARGS)
{
if (TS_CURRENT_LICENSE_IS_APACHE_ONLY())
PG_RETURN_BOOL(false);
PG_RETURN_BOOL(ts_cm_functions->enterprise_enabled_internal());
}
PGDLLEXPORT Datum
ts_current_license_key(PG_FUNCTION_ARGS)
{
if (!is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser or a member of pg_read_all_settings to examine the "
"license key")));
Assert(ts_guc_license_key != NULL);
PG_RETURN_TEXT_P(cstring_to_text(ts_guc_license_key));
}
PGDLLEXPORT Datum
ts_print_tsl_license_expiration_info(PG_FUNCTION_ARGS)
{
if (ts_cm_functions->print_tsl_license_expiration_info_hook != NULL)
ts_cm_functions->print_tsl_license_expiration_info_hook();
PG_RETURN_VOID();
}
PGDLLEXPORT Datum
ts_license_expiration_time(PG_FUNCTION_ARGS)
{
if (ts_cm_functions->print_tsl_license_expiration_info_hook == NULL)
PG_RETURN_TIMESTAMPTZ(DT_NOEND);
PG_RETURN_TIMESTAMPTZ(ts_cm_functions->license_end_time());
}
PGDLLEXPORT Datum
ts_license_edition(PG_FUNCTION_ARGS)
{
char *edition = NULL;
switch (TS_CURRENT_LICENSE_TYPE())
{
case LICENSE_TYPE_APACHE_ONLY:
edition = TS_LICENSE_APACHE_ONLY_PRINT_TEXT;
break;
case LICENSE_TYPE_COMMUNITY:
edition = TS_LICENSE_COMMUNITY_PRINT_TEXT;
break;
case LICENSE_TYPE_ENTERPRISE:
edition = TS_LICENSE_ENTERPRISE_PRINT_TEXT;
break;
default:
elog(ERROR, "Invalid license key '%s'", ts_guc_license_key);
pg_unreachable();
}
PG_RETURN_TEXT_P(cstring_to_text(edition));
}
/*
* For testing purposes we occasionally need the ability to set the license to
* Apache only. This function allows us to bypass the test that usually disables
* that.
*/
PGDLLEXPORT Datum
ts_allow_downgrade_to_apache(PG_FUNCTION_ARGS)
{
downgrade_to_apache_enabled = true;
PG_RETURN_VOID();
if (load_enabled && license_type_of(newval) == LICENSE_TIMESCALE)
tsl_module_init();
}

View File

@ -13,61 +13,22 @@
#include <export.h>
#include <guc.h>
/*
* The license for using only Apache features is 'ApacheOnly'
*
* For documentation on other license types,
* and information on the license format,
* see tsl/src/license.c
* (NB: This requires accepting LICENSE-TIMESCALE)
*/
typedef enum LicenseType
{
LICENSE_TYPE_APACHE_ONLY = 'A',
LICENSE_TYPE_COMMUNITY = 'C',
LICENSE_TYPE_ENTERPRISE = 'E',
} LicenseType;
#define TS_APACHE_ONLY_LICENSE "ApacheOnly"
#define TS_COMMUNITY_LICENSE "CommunityLicense"
#define TS_LICENSE_APACHE "apache"
#define TS_LICENSE_TIMESCALE "timescale"
/*
* If compiled with APACHE_ONLY, default to using only Apache code.
*/
#ifdef APACHE_ONLY
#define TS_DEFAULT_LICENSE TS_APACHE_ONLY_LICENSE
#define TS_LICENSE_DEFAULT TS_LICENSE_APACHE
#else
#define TS_DEFAULT_LICENSE TS_COMMUNITY_LICENSE
#define TS_LICENSE_DEFAULT TS_LICENSE_TIMESCALE
#endif
#define TS_LICENSE_TYPE(license) license[0]
extern bool ts_license_guc_check_hook(char **newval, void **extra, GucSource source);
extern void ts_license_guc_assign_hook(const char *newval, void *extra);
#define TS_LICENSE_TYPE_IS_VALID(license) \
(TS_LICENSE_TYPE(license) == LICENSE_TYPE_APACHE_ONLY || \
TS_LICENSE_TYPE(license) == LICENSE_TYPE_COMMUNITY || \
TS_LICENSE_TYPE(license) == LICENSE_TYPE_ENTERPRISE)
extern TSDLLEXPORT void ts_license_enable_module_loading(void);
extern bool ts_license_is_apache(void);
#define TS_LICENSE_IS_APACHE_ONLY(license) (TS_LICENSE_TYPE(license) == LICENSE_TYPE_APACHE_ONLY)
#define TS_CURRENT_LICENSE_TYPE() TS_LICENSE_TYPE(ts_guc_license_key)
#define TS_CURRENT_LICENSE_IS_APACHE_ONLY() TS_LICENSE_IS_APACHE_ONLY(ts_guc_license_key)
/*
* guc updating happens in two parts:
* 1. The new guc value is validated, and any fallible code is run, but no
* externally-visible changes are performed
* 2. The guc is set, and the externally-visible changes are performed
*
* This means that `ts_license_update_check` should not actually change anything
* except for its `extra` parameter. (We cheat a little since in that we might
* load a dynamic library during the `ts_license_update_check`, but we don't
* consider that change to be visible.)
*/
/* Each of these functions takes a LicenseUpdateExtra for their extra param */
extern bool ts_license_update_check(char **newval, void **extra, GucSource source);
extern void ts_license_on_assign(const char *newval, void *extra);
extern void TSDLLEXPORT ts_license_enable_module_loading(void);
#endif /* LICENSE_GUC */
#endif /* TIMESCALEDB_LICENSE_GUC_H */

View File

@ -59,6 +59,7 @@
#define REQ_RELATED_EXTENSIONS "related_extensions"
#define REQ_METADATA "db_metadata"
#define REQ_LICENSE_EDITION_APACHE "apache_only"
#define REQ_LICENSE_EDITION_COMMUNITY "community"
#define REQ_TS_LAST_TUNE_TIME "last_tuned_time"
#define REQ_TS_LAST_TUNE_VERSION "last_tuned_version"
#define REQ_INSTANCE_METADATA "instance_metadata"
@ -376,22 +377,21 @@ build_version_body(void)
pushJsonbValue(&parse_state, WJB_KEY, &ext_key);
add_related_extensions(parse_state);
if (TS_CURRENT_LICENSE_IS_APACHE_ONLY())
{
/* license */
license_info_key.type = jbvString;
license_info_key.val.string.val = REQ_LICENSE_INFO;
license_info_key.val.string.len = strlen(REQ_LICENSE_INFO);
pushJsonbValue(&parse_state, WJB_KEY, &license_info_key);
pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);
/* license */
license_info_key.type = jbvString;
license_info_key.val.string.val = REQ_LICENSE_INFO;
license_info_key.val.string.len = strlen(REQ_LICENSE_INFO);
pushJsonbValue(&parse_state, WJB_KEY, &license_info_key);
pushJsonbValue(&parse_state, WJB_BEGIN_OBJECT, NULL);
if (ts_license_is_apache())
ts_jsonb_add_str(parse_state, REQ_LICENSE_EDITION, REQ_LICENSE_EDITION_APACHE);
pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);
}
else
{
/* add license and distributed database fields */
ts_jsonb_add_str(parse_state, REQ_LICENSE_EDITION, REQ_LICENSE_EDITION_COMMUNITY);
pushJsonbValue(&parse_state, WJB_END_OBJECT, NULL);
/* add distributed database fields */
if (!ts_license_is_apache())
ts_cm_functions->add_tsl_telemetry_info(&parse_state);
}
/* add tuned info, which is optional */
if (ts_last_tune_time != NULL)

View File

@ -1,114 +0,0 @@
-- 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.
\c :TEST_DBNAME :ROLE_SUPERUSER
SELECT _timescaledb_internal.current_license_key();
current_license_key
---------------------
CommunityLicense
(1 row)
SELECT _timescaledb_internal.tsl_loaded();
tsl_loaded
------------
t
(1 row)
SELECT _timescaledb_internal.enterprise_enabled();
enterprise_enabled
--------------------
f
(1 row)
\unset ECHO
SET timescaledb.license_key='ApacheOnly';
ERROR: invalid value for parameter "timescaledb.license_key": "ApacheOnly"
DETAIL: Cannot downgrade a running session to Apache Only.
HINT: change the license in the configuration file
SELECT allow_downgrade_to_apache();
allow_downgrade_to_apache
---------------------------
(1 row)
SET timescaledb.license_key='ApacheOnly';
select * from timescaledb_information.license;
edition | expired | expiration_time
---------+---------+-----------------
apache | f | infinity
(1 row)
SELECT _timescaledb_internal.current_db_set_license_key('CommunityLicense');
current_db_set_license_key
----------------------------
CommunityLicense
(1 row)
select * from timescaledb_information.license;
edition | expired | expiration_time
-----------+---------+-----------------
community | f | infinity
(1 row)
create or replace function get_sqlstate(in_text TEXT) RETURNS TEXT AS
$$
BEGIN
BEGIN
EXECUTE in_text;
EXCEPTION WHEN others THEN GET STACKED DIAGNOSTICS in_text = RETURNED_SQLSTATE;
END;
RETURN in_text;
END;
$$
LANGUAGE PLPGSQL;
SELECT * FROM timescaledb_information.license;
edition | expired | expiration_time
-----------+---------+-----------------
community | f | infinity
(1 row)
SELECT * FROM _timescaledb_internal.enterprise_enabled();
enterprise_enabled
--------------------
f
(1 row)
SELECT * FROM _timescaledb_internal.tsl_loaded();
tsl_loaded
------------
t
(1 row)
SELECT * FROM _timescaledb_internal.license_expiration_time();
license_expiration_time
-------------------------
infinity
(1 row)
SELECT * FROM _timescaledb_internal.print_license_expiration_info();
print_license_expiration_info
-------------------------------
(1 row)
SELECT * FROM _timescaledb_internal.license_edition();
license_edition
-----------------
community
(1 row)
SELECT get_sqlstate($$SELECT _timescaledb_internal.current_db_set_license_key('ApacheOnly')$$);
get_sqlstate
--------------
42501
(1 row)
SELECT get_sqlstate($$SHOW timescaledb.license_key;$$);
get_sqlstate
--------------
42501
(1 row)
SELECT * FROM _timescaledb_internal.current_license_key();
ERROR: must be superuser or a member of pg_read_all_settings to examine the license key
drop function get_sqlstate(TEXT);

View File

@ -1,20 +0,0 @@
-- 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.
\unset ECHO
SELECT allow_downgrade_to_apache();
allow_downgrade_to_apache
---------------------------
(1 row)
SET timescaledb.license_key='ApacheOnly';
SELECT locf(1);
ERROR: function "locf" is not supported under the current license "ApacheOnly"
HINT: Upgrade to a Timescale-licensed binary to access this free community feature
SELECT interpolate(1);
ERROR: function "interpolate" is not supported under the current license "ApacheOnly"
HINT: Upgrade to a Timescale-licensed binary to access this free community feature
SELECT time_bucket_gapfill(1,1,1,1);
ERROR: function "time_bucket_gapfill" is not supported under the current license "ApacheOnly"
HINT: Upgrade to a Timescale-licensed binary to access this free community feature

58
test/expected/license.out Normal file
View File

@ -0,0 +1,58 @@
-- 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.
\c :TEST_DBNAME :ROLE_SUPERUSER
\set ECHO queries
SHOW timescaledb.license;
timescaledb.license
---------------------
apache
(1 row)
SELECT _timescaledb_internal.tsl_loaded();
tsl_loaded
------------
f
(1 row)
SET timescaledb.license='apache';
ERROR: invalid value for parameter "timescaledb.license": "apache"
DETAIL: Cannot change a license in a running session.
HINT: Change the license in the configuration file or server command line.
SET timescaledb.license='timescale';
ERROR: invalid value for parameter "timescaledb.license": "timescale"
DETAIL: Cannot change a license in a running session.
HINT: Change the license in the configuration file or server command line.
SET timescaledb.license='something_else';
ERROR: invalid value for parameter "timescaledb.license": "something_else"
DETAIL: Unrecognized license type.
HINT: Supported license types are 'timescale' or 'apache'.
SELECT locf(1);
ERROR: function "locf" is not supported under the current "apache" license
HINT: Upgrade your license to 'timescale' to use this free community feature.
SELECT interpolate(1);
ERROR: function "interpolate" is not supported under the current "apache" license
HINT: Upgrade your license to 'timescale' to use this free community feature.
SELECT time_bucket_gapfill(1,1,1,1);
ERROR: function "time_bucket_gapfill" is not supported under the current "apache" license
HINT: Upgrade your license to 'timescale' to use this free community feature.
CREATE OR REPLACE FUNCTION custom_func(jobid int, args jsonb) RETURNS VOID AS $$
DECLARE
BEGIN
END;
$$ LANGUAGE plpgsql;
SELECT add_job('custom_func','1h', config:='{"type":"function"}'::jsonb);
ERROR: function "add_job" is not supported under the current "apache" license
HINT: Upgrade your license to 'timescale' to use this free community feature.
DROP FUNCTION custom_func;
CREATE TABLE metrics(time timestamptz NOT NULL, value float);
SELECT create_hypertable('metrics', 'time');
create_hypertable
----------------------
(1,public,metrics,t)
(1 row)
ALTER TABLE metrics SET (timescaledb.compress);
ERROR: functionality not supported under the current "apache" license
HINT: Upgrade your license to 'timescale' to use this free community feature.
DROP TABLE metrics;

View File

@ -542,7 +542,6 @@ WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass AND
timescaledb_information.continuous_aggregates
timescaledb_information.jobs
timescaledb_information.job_stats
timescaledb_information.license
timescaledb_information.hypertables
_timescaledb_internal.compressed_chunk_stats
_timescaledb_internal.hypertable_chunk_local_size
@ -550,7 +549,7 @@ WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass AND
_timescaledb_internal.bgw_policy_chunk_stats
_timescaledb_internal.bgw_job_stat
_timescaledb_catalog.tablespace_id_seq
(15 rows)
(14 rows)
-- Make sure we can't run our restoring functions as a normal perm user as that would disable functionality for the whole db
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER

View File

@ -6,6 +6,7 @@ shared_preload_libraries=timescaledb
max_worker_processes=16
autovacuum=false
random_page_cost=1.0
timescaledb.license='apache'
timescaledb.telemetry_level=off
timescaledb.last_tuned='1971-02-03 04:05:06.789012 -0300'
timescaledb.last_tuned_version='0.0.1'

View File

@ -21,7 +21,6 @@ set(TEST_FILES
drop_schema.sql
dump_meta.sql
extension.sql
gapfill.sql
generated_as_identity.sql
grant_hypertable.sql
hash.sql
@ -51,23 +50,14 @@ set(TEST_FILES
vacuum.sql
version.sql
views.sql
license.sql
)
# edition tests need community edition
if(NOT APACHE_ONLY)
list(APPEND TEST_FILES
chunk_utils_compression.sql
edition.sql
telemetry_community.sql
)
endif()
# tests that fail or are unreliable when run in parallel
# bgw tests need to run first otherwise they are flaky
set(SOLO_TESTS
alter
alternate_users
bgw_db_scheduler
bgw_launcher
index
net
@ -79,7 +69,6 @@ set(SOLO_TESTS
if (CMAKE_BUILD_TYPE MATCHES Debug)
list(APPEND TEST_FILES
bgw_launcher.sql
bgw_db_scheduler.sql
c_unit_tests.sql
guc_options.sql
loader.sql

View File

@ -1,63 +0,0 @@
-- 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.
\c :TEST_DBNAME :ROLE_SUPERUSER
SELECT _timescaledb_internal.current_license_key();
SELECT _timescaledb_internal.tsl_loaded();
SELECT _timescaledb_internal.enterprise_enabled();
\unset ECHO
\o /dev/null
\ir include/test_utils.sql
\o
\set ECHO queries
\set VERBOSITY default
\c :TEST_DBNAME :ROLE_SUPERUSER
\set ON_ERROR_STOP 0
SET timescaledb.license_key='ApacheOnly';
\set ON_ERROR_STOP 1
SELECT allow_downgrade_to_apache();
SET timescaledb.license_key='ApacheOnly';
select * from timescaledb_information.license;
SELECT _timescaledb_internal.current_db_set_license_key('CommunityLicense');
select * from timescaledb_information.license;
-- Default perm user shouldn't be able to change the license key.
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER
-- Hides error messages in cases where error messages differe between Postgres versions
create or replace function get_sqlstate(in_text TEXT) RETURNS TEXT AS
$$
BEGIN
BEGIN
EXECUTE in_text;
EXCEPTION WHEN others THEN GET STACKED DIAGNOSTICS in_text = RETURNED_SQLSTATE;
END;
RETURN in_text;
END;
$$
LANGUAGE PLPGSQL;
--allowed
SELECT * FROM timescaledb_information.license;
SELECT * FROM _timescaledb_internal.enterprise_enabled();
SELECT * FROM _timescaledb_internal.tsl_loaded();
SELECT * FROM _timescaledb_internal.license_expiration_time();
SELECT * FROM _timescaledb_internal.print_license_expiration_info();
SELECT * FROM _timescaledb_internal.license_edition();
--disallowd
\set ON_ERROR_STOP 0
SELECT get_sqlstate($$SELECT _timescaledb_internal.current_db_set_license_key('ApacheOnly')$$);
SELECT get_sqlstate($$SHOW timescaledb.license_key;$$);
SELECT * FROM _timescaledb_internal.current_license_key();
\set ON_ERROR_STOP 1
drop function get_sqlstate(TEXT);

View File

@ -1,21 +0,0 @@
-- 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.
\unset ECHO
\o /dev/null
\ir include/test_utils.sql
\o
\set ECHO queries
\set VERBOSITY default
\c :TEST_DBNAME :ROLE_SUPERUSER
SELECT allow_downgrade_to_apache();
SET timescaledb.license_key='ApacheOnly';
\set ON_ERROR_STOP 0
SELECT locf(1);
SELECT interpolate(1);
SELECT time_bucket_gapfill(1,1,1,1);
\set ON_ERROR_STOP 1

View File

@ -27,12 +27,3 @@ BEGIN
END IF;
END
$BODY$;
\c :DBNAME :ROLE_SUPERUSER
CREATE OR REPLACE FUNCTION allow_downgrade_to_apache()
RETURNS VOID
AS :MODULE_PATHNAME, 'ts_allow_downgrade_to_apache'
LANGUAGE C;
\c :DBNAME :ROLE_DEFAULT_PERM_USER

40
test/sql/license.sql Normal file
View File

@ -0,0 +1,40 @@
-- 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.
\c :TEST_DBNAME :ROLE_SUPERUSER
\set ECHO queries
\set VERBOSITY default
SHOW timescaledb.license;
SELECT _timescaledb_internal.tsl_loaded();
-- User shouldn't be able to change the license in the session
\set ON_ERROR_STOP 0
SET timescaledb.license='apache';
SET timescaledb.license='timescale';
SET timescaledb.license='something_else';
\set ON_ERROR_STOP 1
-- make sure apache license blocks tsl features
\set ON_ERROR_STOP 0
SELECT locf(1);
SELECT interpolate(1);
SELECT time_bucket_gapfill(1,1,1,1);
CREATE OR REPLACE FUNCTION custom_func(jobid int, args jsonb) RETURNS VOID AS $$
DECLARE
BEGIN
END;
$$ LANGUAGE plpgsql;
SELECT add_job('custom_func','1h', config:='{"type":"function"}'::jsonb);
DROP FUNCTION custom_func;
CREATE TABLE metrics(time timestamptz NOT NULL, value float);
SELECT create_hypertable('metrics', 'time');
ALTER TABLE metrics SET (timescaledb.compress);
DROP TABLE metrics;
\set ON_ERROR_STOP 1

View File

@ -20,10 +20,6 @@ set_target_properties(${TESTS_LIB_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_compile_definitions(${TESTS_LIB_NAME} PUBLIC MODULE_NAME=timescaledb)
target_include_directories(${TESTS_LIB_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
if(APACHE_ONLY)
add_definitions(-DAPACHE_ONLY)
endif()
add_subdirectory(bgw)
add_subdirectory(net)
add_subdirectory(telemetry)

View File

@ -32,8 +32,8 @@ static post_parse_analyze_hook_type prev_post_parse_analyze_hook;
bool ts_extension_invalidate(Oid relid);
bool ts_extension_is_loaded(void);
void ts_extension_check_version(const char *actual_version);
bool ts_license_update_check(char **newval, void **extra, GucSource source);
void ts_license_on_assign(const char *newval, void *extra);
bool ts_license_guc_check_hook(char **newval, void **extra, GucSource source);
void ts_license_guc_assign_hook(const char *newval, void *extra);
TS_FUNCTION_INFO_V1(ts_post_load_init);
@ -121,12 +121,12 @@ ts_post_load_init(PG_FUNCTION_ARGS)
}
bool
ts_license_update_check(char **newval, void **extra, GucSource source)
ts_license_guc_check_hook(char **newval, void **extra, GucSource source)
{
return true;
}
void
ts_license_on_assign(const char *newval, void *extra)
ts_license_guc_assign_hook(const char *newval, void *extra)
{
}

View File

@ -8,7 +8,6 @@ set(SOURCES
dist_util.c
hypertable.c
init.c
license.c
partialize_finalize.c
planner.c
process_utility.c

35
tsl/src/README.module.md Normal file
View File

@ -0,0 +1,35 @@
# Submodule Licensing and Initialization #
## Loading and Activation ##
We link module loading and activation to the license GUC itself.
We have a single GUC, the license, and load submodules based on
what capabilities the license enables, i.e., an `apache` license-key does
not load this module, while `timescale` key does. This
ensures that the loader "does the right thing" with respect to the license, and
a user cannot accidentally activate features they aren't licensed to use.
The actual loading and activation is done through `check` and `assign` hooks on
the license GUC. On `check` we validate the license type
and on `assign` we set the capabilities-struct in this module,
if needed. The `check` and `assign` functions can be found in
[`license_guc.c/h`](/src/license_guc.c) in the Apache-Licensed src.
### Cross License Functions ###
To enable binaries which only contain Apache-Licensed code,
we dynamically link in Timescale-Licensed code on license activation,
and handle all function calls into the module via function pointers.
The registry in `ts_cm_functions` of type `CrossModuleFunctions` (declared in
[`cross_module_fn.h`](/src/cross_module_fn.h) and defined in
[`cross_module_fn.c`](/src/cross_module_fn.c)) stores all of the cross-module
functions.
To add a new cross-module function you must:
- Add a struct member `CrossModuleFunctions.<function name>`.
- Add default function to `ts_cm_functions_default` that will be called from the Apache version, usually this function should just call `error_no_default_fn`. **NOTE** Due to function-pointer casting rules, the default function must have the exact same signature as the function pointer; you may _not_ cast another function pointer of another type.
- Add the overriding function to `tsl_cm_functions`in `init.c` in this module.
To call a cross-module functions use `ts_cm_functions-><function name>(args)`.

View File

@ -1,82 +0,0 @@
# Submodule Licensing and Initialization #
## Loading and Activation ##
We link module loading and activation to the license key GUC itself.
While keeping multiple GUCs, e.g., one for module loading and one licensing,
is tempting, it leads to hairy edge cases when the GUCs disagree.
Instead, we have a single GUC, the license key, and load submodules based on
what capabilities the license key enables, i.e., an `ApacheOnly` license-key does
not load this module, while an `Enterprise` or `Community` key does. This
ensures that the loader "does the right thing" with respect to the license, and
a user cannot accidentally activate features they aren't licensed to use.
The actual loading and activation is done through `check` and `assign` hooks on
the license key GUC. On `check` we validate the license-key and deserialize it
if needed, and on `assign` we set the capabilities-struct in this module,
if needed. The `check` and `assign` functions can be found in
[`license_guc.c/h`](/src/license_guc.c) in the Apache-Licensed src, and the
validation code is in [`license.c/h`](./license.c) in this module.
### Cross License Functions ###
To enable binaries which only contain Apache-Licensed code,
we dynamically link in Timescale-Licensed code on license key activation,
and handle all function calls into the module via function pointers.
The registry in `ts_cm_functions` of type `CrossModuleFunctions` (declared in
[`cross_module_fn.h`](/src/cross_module_fn.h) and defined in
[`cross_module_fn.c`](/src/cross_module_fn.c)) stores all of the cross-module
functions. To add a new cross-module function you must:
- Add a struct member `CrossModuleFunctions.<function name>`.
- Add default function to `ts_cm_functions_default` that will be called from the Apache version, usually this function should just call `error_no_default_fn`. **NOTE** Due to function-pointer casting rules, the default function must have the exact same signature as the function pointer; you may _not_ cast another function pointer of another type.
- Add the overriding function to `tsl_cm_functions`in `init.c` in this module.
To call a cross-module functions use `ts_cm_functions-><function name>(args)`.
## License Validation ##
We want to keep all license verification code under the Timescale License;
code under the Apache License is freely modifiable, and if we include our
license-verification code there it may be possible to remove without violating
the license (_using_ the module without a valid license would still be in
violation).
Therefore we take a two stage approach to license validation:
1. In the Apache code we read the first character of the license to determine what type of license this is. This is enough to let us know if we need to load this module at all; ApacheOnly licenses can simply stop after this step.
2. If the license requires it (Community and Enterprise), we load this module and use `tsl_license_update_check` to check that the license is valid. This function returns whether a given license-string is a valid license, and optionally returns a deserialized representation of the licenses capabilities.
## License Format ##
In a simplified form the BNF for a valid license key is
```ABNF
LicenseKey = ApacheLicense / CommunityLicense / EnterpriseLicenseV1
ApacheLicense = "A" char*
CommunityLicense = "C" char*
EnterpriseLicense = "E" EnterpriseLicenseContents
EnterpriseLicenseContents = "1" <base64 encoded JSONLicenseInfo>
```
where a `JSONLicenseInfo` describes the capabilities granted by the license.
It is JSON corresponding to:
```JSON
{
"id":"5D96CF28-87A6-48B5-AE8E-99CF851ABE0C", //uuid identifying the license
"type":"trial", // either "trial" or "commercial"
"start_time":"2018/10/01", //datetime at which the license was generated
"end_time":"2018/10/31" //datetime at which the license expires
}
```
That is, an ApacheOnly license is an `'A'` followed by any string, the canonical
version of this license is `"ApacheOnly"`; a Community License is `'C'` followed
by any string, the canonical version being `'CommunityLicense'`; and an
Enterprise License is `"E1"` followed by a base64 encoded JSON containing the
capabilities the license enables.

View File

@ -14,7 +14,6 @@
#include "errors.h"
#include "hypertable.h"
#include "hypertable_cache.h"
#include "license.h"
#include "policy_utils.h"
#include "utils.h"
#include "jsonb_utils.h"

View File

@ -44,7 +44,6 @@
#include "dimension_vector.h"
#include "errors.h"
#include "job.h"
#include "license.h"
#include "reorder.h"
#include "utils.h"

View File

@ -23,7 +23,6 @@
#include "bgw_policy/reorder_api.h"
#include "errors.h"
#include "hypertable.h"
#include "license.h"
#include "reorder.h"
#include "utils.h"

View File

@ -21,7 +21,6 @@
#include "errors.h"
#include "hypertable.h"
#include "dimension.h"
#include "license.h"
#include "policy_utils.h"
#include "utils.h"
#include "jsonb_utils.h"

View File

@ -36,7 +36,6 @@
#include "compat.h"
#include "scanner.h"
#include "scan_iterator.h"
#include "license.h"
#include "compression_chunk_size.h"
#define CHUNK_DML_BLOCKER_TRIGGER "chunk_dml_blocker"

View File

@ -39,7 +39,6 @@
#include "hypertable_cache.h"
#include "hypertable_compression.h"
#include "custom_type_cache.h"
#include "license.h"
#include "trigger.h"
#include "utils.h"

View File

@ -21,7 +21,6 @@
#include "dimension.h"
#include "errors.h"
#include "hypertable.h"
#include "license.h"
#include "utils.h"
#include "hypertable_cache.h"
#include "chunk.h"

View File

@ -35,7 +35,6 @@
#include "fdw/fdw.h"
#include "hypertable.h"
#include "license_guc.h"
#include "license.h"
#include "nodes/decompress_chunk/planner.h"
#include "nodes/gapfill/gapfill.h"
#include "partialize_finalize.h"
@ -63,10 +62,6 @@ PG_MODULE_MAGIC;
extern void PGDLLEXPORT _PG_init(void);
extern void PGDLLEXPORT _PG_fini(void);
static void module_shutdown(void);
static bool enterprise_enabled_internal(void);
static bool check_tsl_loaded(void);
static void
cache_syscache_invalidate(Datum arg, int cacheid, uint32 hashvalue)
{
@ -84,12 +79,6 @@ cache_syscache_invalidate(Datum arg, int cacheid, uint32 hashvalue)
* Apache codebase.
*/
CrossModuleFunctions tsl_cm_functions = {
.tsl_license_on_assign = tsl_license_on_assign,
.enterprise_enabled_internal = enterprise_enabled_internal,
.check_tsl_loaded = check_tsl_loaded,
.license_end_time = license_end_time,
.print_tsl_license_expiration_info_hook = license_print_expiration_info,
.module_shutdown_hook = module_shutdown,
.add_tsl_telemetry_info = tsl_telemetry_add_info,
.create_upper_paths_hook = tsl_create_upper_paths_hook,
@ -213,40 +202,8 @@ ts_module_init(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(true);
}
/*
* Currently we disallow shutting down this submodule in a live session,
* but if we did, this would be the function we'd use.
*/
static void
module_shutdown(void)
{
_continuous_aggs_cache_inval_fini();
/*
* Order of items should be strict reverse order of ts_module_init. Please
* document any exceptions.
*/
_remote_dist_txn_fini();
_remote_connection_cache_fini();
_tsl_process_utility_fini();
ts_cm_functions = &ts_cm_functions_default;
}
/* Informative functions */
static bool
enterprise_enabled_internal(void)
{
return license_enterprise_enabled();
}
static bool
check_tsl_loaded(void)
{
return true;
}
PGDLLEXPORT void
_PG_init(void)
{
@ -255,7 +212,7 @@ _PG_init(void)
* timescale library is loaded, after which we enable it from the loader.
* In parallel workers the restore shared libraries function will load the
* libraries itself, and we bypass the loader, so we need to ensure that
* timescale is aware it can use the tsl if needed. It is always safe to
* timescale is aware it can use the tsl if needed. It is always safe to
* do this here, because if we reach this point, we must have already
* loaded the tsl, so we no longer need to worry about its load order
* relative to the other libraries.

View File

@ -1,443 +0,0 @@
/*
* 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.
*/
/*
* A license is a type followed by the license contents, see Readme.module.md
* for more detail.
*/
#include <postgres.h>
#include <access/xact.h>
#include <common/base64.h>
#include <utils/builtins.h>
#include <utils/datetime.h>
#include <utils/elog.h>
#include <utils/jsonb.h>
#include <utils/memutils.h>
#include <export.h>
#include <license_guc.h>
#include <jsonb_utils.h>
#include "license.h"
#define LICENSE_MAX_ID_LEN 40
#define LICENSE_MAX_KIND_LEN 16
typedef struct LicenseInfo
{
/*
* Leaving id and kind a strings for now since they're only used for
* telemetry. If we start introspecting on one of them we should switch it
* to a more structured type.
*/
char id[LICENSE_MAX_ID_LEN];
char kind[LICENSE_MAX_KIND_LEN];
TimestampTz start_time;
TimestampTz end_time;
bool enterprise_features_enabled;
} LicenseInfo;
static bool license_deserialize_enterprise(char *license, LicenseInfo *license_out);
static bool license_info_init_from_base64(char *license_key, LicenseInfo *out);
static void license_info_init_from_jsonb(Jsonb *json_license, LicenseInfo *out);
static bool validate_license_info(const LicenseInfo *license);
static LicenseInfo current_license = {
.id = { 0 },
.kind = { 0 },
.end_time = DT_NOBEGIN,
.enterprise_features_enabled = false,
};
static const LicenseInfo no_license = {
.id = "",
.kind = { "" },
.start_time = DT_NOBEGIN,
.end_time = DT_NOBEGIN,
.enterprise_features_enabled = false,
};
static const LicenseInfo community_license = { .id = "",
.kind = { "" },
.start_time = DT_NOBEGIN,
.end_time = DT_NOEND,
.enterprise_features_enabled = false };
static bool printed_license_expiration_warning = false;
TS_FUNCTION_INFO_V1(tsl_license_update_check);
Datum
tsl_license_update_check(PG_FUNCTION_ARGS)
{
bool license_deserialized;
char *license_key = NULL;
LicenseInfo **guc_extra = NULL;
LicenseInfo license_info = { { 0 } };
Assert(!PG_ARGISNULL(0));
Assert(!PG_ARGISNULL(1));
license_key = PG_GETARG_CSTRING(0);
guc_extra = (LicenseInfo **) PG_GETARG_POINTER(1);
license_deserialized = license_deserialize_enterprise(license_key, &license_info);
if (guc_extra != NULL)
{
/*
* According to the postgres guc documentation, string `extra`s MUST
* be allocated with `malloc`. (postgres attempts to `free` unneeded
* guc-extras with the system `free` upon transaction commit, and
* there's no guarantee that any MemoryContext uses the correct
* allocator.) However, there is a bug in Windows which causes heap
* corruption if we try to do that. Instead we currently rerun this
* function during the assign, with the assumption that since we
* called the function during the check hook it cannot fail now.
*/
*guc_extra = malloc(sizeof(LicenseInfo));
memcpy(*guc_extra, &license_info, sizeof(LicenseInfo));
}
PG_RETURN_BOOL(license_deserialized && validate_license_info(&license_info));
}
/*
* Return if a license is valid, optionally outputting the deserialized form.
*/
static bool
license_deserialize_enterprise(char *license_key, LicenseInfo *license_out)
{
MemoryContext old_ctx;
MemoryContext deserialize_ctx;
LicenseInfo license_temp = { { 0 } };
const LicenseInfo *license_info = NULL;
size_t license_key_len = strlen(license_key);
if (license_key_len < 1)
return false;
switch (TS_LICENSE_TYPE(license_key))
{
case LICENSE_TYPE_APACHE_ONLY:
license_info = &no_license;
break;
case LICENSE_TYPE_COMMUNITY:
license_info = &community_license;
break;
case LICENSE_TYPE_ENTERPRISE:
if (license_key_len < 2)
return false;
/*
* Second byte of an enterprise license key is the version.
* Hardcoding this for now since we only have one version. the
* byte corresponding to '0' is reserved in case we need to extend
* the length of the version number.
*/
if (license_key[1] != '1')
return false;
/* create a memory context so all temporary alloctions will be freed
* when this is called during initialization, it will not happen
* automatically
*/
deserialize_ctx = AllocSetContextCreate(CurrentMemoryContext,
"license deserialize",
ALLOCSET_SMALL_SIZES);
old_ctx = MemoryContextSwitchTo(deserialize_ctx);
if (license_info_init_from_base64(license_key + 2, &license_temp))
license_info = &license_temp;
MemoryContextSwitchTo(old_ctx);
MemoryContextDelete(deserialize_ctx);
break;
default:
return false;
}
if (NULL == license_info)
return false;
if (license_out != NULL)
memmove(license_out, license_info, sizeof(*license_info));
return true;
}
static bool
validate_license_info(const LicenseInfo *license)
{
if (license->enterprise_features_enabled)
{
if (strcmp(license->kind, "trial") != 0 && strcmp(license->kind, "commercial") != 0)
return false;
}
if (timestamp_cmp_internal(license->end_time, license->start_time) < 0)
return false;
return true;
}
/*****************************************************************************
*****************************************************************************/
static char *base64_decode(char *license_key);
static bool
license_info_init_from_base64(char *license_key, LicenseInfo *out)
{
char *expanded = base64_decode(license_key);
if (expanded == NULL)
return false;
PG_TRY();
{
Datum json_key = DirectFunctionCall1(jsonb_in, CStringGetDatum(expanded));
license_info_init_from_jsonb((Jsonb *) DatumGetPointer(json_key), out);
}
PG_CATCH();
{
#ifdef TS_DEBUG
EmitErrorReport();
#endif
return false;
}
PG_END_TRY();
return true;
}
static char *
base64_decode(char *license_key)
{
int raw_len = strlen(license_key);
int decoded_buffer_len = pg_b64_dec_len(raw_len) + 1;
char *decoded = palloc(decoded_buffer_len);
int decoded_len = pg_b64_decode(license_key, raw_len, decoded);
if (decoded_len < 0)
return NULL;
Assert(decoded_len < decoded_buffer_len);
decoded[decoded_len] = '\0';
return decoded;
}
/*****************************************************************************
*****************************************************************************/
/*
* JSON license decoding
*/
#define ID_FIELD "id"
#define KIND_FIELD "kind"
#define START_TIME_FIELD "start_time"
#define END_TIME_FIELD "end_time"
#define FIELD_NOT_FOUND_ERRSTRING "invalid license key for TimescaleDB, could not find field \"%s\""
static char *json_get_id(Jsonb *license);
static char *json_get_kind(Jsonb *license);
static TimestampTz json_get_start_time(Jsonb *license);
static TimestampTz json_get_end_time(Jsonb *license);
static void
license_info_init_from_jsonb(Jsonb *json_license, LicenseInfo *out)
{
char *id_str = json_get_id(json_license);
if (id_str == NULL)
elog(ERROR, "missing id in license key");
StrNCpy(out->id, id_str, sizeof(out->id));
StrNCpy(out->kind, json_get_kind(json_license), sizeof(out->kind));
out->start_time = json_get_start_time(json_license);
out->end_time = json_get_end_time(json_license);
out->enterprise_features_enabled = true;
}
static char *
json_get_id(Jsonb *license)
{
return ts_jsonb_get_str_field(license, ID_FIELD);
}
static char *
json_get_kind(Jsonb *license)
{
return ts_jsonb_get_str_field(license, KIND_FIELD);
}
static TimestampTz
json_get_start_time(Jsonb *license)
{
bool found = false;
TimestampTz start_time = ts_jsonb_get_time_field(license, START_TIME_FIELD, &found);
if (!found)
elog(ERRCODE_FEATURE_NOT_SUPPORTED, FIELD_NOT_FOUND_ERRSTRING, START_TIME_FIELD);
return start_time;
}
static TimestampTz
json_get_end_time(Jsonb *license)
{
bool found = false;
TimestampTz end_time = ts_jsonb_get_time_field(license, END_TIME_FIELD, &found);
if (!found)
elog(ERRCODE_FEATURE_NOT_SUPPORTED, FIELD_NOT_FOUND_ERRSTRING, END_TIME_FIELD);
return end_time;
}
/*****************************************************************************
*****************************************************************************
*****************************************************************************/
static bool license_info_is_expired(const LicenseInfo *license);
/*
* We don't want to expose the LicenseInfo struct to the Apache code, so we use
* this intermediate function to translate from the `void *` to `LicenseInfo *`.
* There are some cases where the guc extra may not be set, so if this function
* receives a NULL license, it will translate that to `no_license`
*/
void
tsl_license_on_assign(const char *newval, const void *license)
{
/*
* A NULL extra means that we're reverting to the top of the license guc
* stack.
*/
if (license == NULL)
{
license_switch_to(&no_license);
return;
}
license_switch_to(license);
}
void
license_switch_to(const LicenseInfo *license)
{
Assert(license != NULL);
current_license = *license;
}
bool
license_info_is_expired(const LicenseInfo *license)
{
TimestampTz current_time = GetCurrentTransactionStartTimestamp();
return timestamp_cmp_internal(license->end_time, current_time) < 0;
}
/*****************************************************************************
*****************************************************************************
*****************************************************************************/
/* Getters for current license */
bool
license_is_expired()
{
return license_info_is_expired(&current_license);
}
bool
license_enterprise_enabled(void)
{
return current_license.enterprise_features_enabled;
}
char *
license_kind_str(void)
{
return current_license.kind;
}
char *
license_id_str(void)
{
return current_license.id;
}
TimestampTz
license_start_time(void)
{
return current_license.start_time;
}
TimestampTz
license_end_time(void)
{
return current_license.end_time;
}
void
license_enforce_enterprise_enabled(void)
{
if (!license_enterprise_enabled())
elog(ERROR, "cannot execute an enterprise function with an invalid enterprise license");
}
void
license_print_expiration_info(void)
{
if (!TIMESTAMP_NOT_FINITE(current_license.end_time) &&
current_license.enterprise_features_enabled)
{
ereport(NOTICE,
(errcode(ERRCODE_WARNING),
errmsg("your Timescale Enterprise License expires on %s",
DatumGetCString(
DirectFunctionCall1(timestamptz_out, current_license.end_time)))));
}
else
{
printed_license_expiration_warning = false;
license_print_expiration_warning_if_needed();
}
}
void
license_print_expiration_warning_if_needed(void)
{
if (printed_license_expiration_warning)
return;
printed_license_expiration_warning = true;
if (license_is_expired())
ereport(WARNING,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("Timescale License expired"),
errhint("Your license expired on %s. Renew your license to continue using "
"enterprise features.",
DatumGetCString(
DirectFunctionCall1(timestamptz_out,
TimestampTzGetDatum(current_license.end_time))))));
else
{
Interval week = {
.day = 7,
};
TimestampTz warn_after =
DatumGetTimestampTz(DirectFunctionCall2(timestamptz_mi_interval,
TimestampTzGetDatum(current_license.end_time),
IntervalPGetDatum(&week)));
if (timestamp_cmp_internal(GetCurrentTransactionStartTimestamp(), warn_after) >= 0)
ereport(WARNING,
(errcode(ERRCODE_WARNING),
errmsg("your Timescale Enterprise License expires on %s",
DatumGetCString(DirectFunctionCall1(timestamptz_out,
TimestampTzGetDatum(
current_license.end_time))))));
}
}

View File

@ -1,31 +0,0 @@
/*
* 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.
*/
#ifndef TIMESCALEDB_TSL_LICENSE_H
#define TIMESCALEDB_TSL_LICENSE_H
#include <postgres.h>
#include <c.h>
#include <export.h>
#include <utils/jsonb.h>
#include <utils/timestamp.h>
typedef struct LicenseInfo LicenseInfo;
extern PGDLLEXPORT Datum tsl_license_update_check(PG_FUNCTION_ARGS);
extern void tsl_license_on_assign(const char *newval, const void *license);
extern void license_switch_to(const LicenseInfo *license);
bool license_is_expired(void);
bool license_enterprise_enabled(void);
char *license_kind_str(void);
char *license_id_str(void);
TimestampTz license_start_time(void);
TimestampTz license_end_time(void);
void license_enforce_enterprise_enabled(void);
void license_print_expiration_info(void);
void license_print_expiration_warning_if_needed(void);
#endif /* TIMESCALEDB_TSL_LICENSE_H */

View File

@ -7,7 +7,6 @@
#include <fmgr.h>
#include <utils/lsyscache.h>
#include "license.h"
#include "time_bucket.h"
#include "nodes/gapfill/gapfill.h"

View File

@ -23,7 +23,6 @@
#include <optimizer/optimizer.h>
#endif
#include "license.h"
#include "nodes/gapfill/gapfill.h"
#include "nodes/gapfill/planner.h"
#include "nodes/gapfill/exec.h"

View File

@ -62,7 +62,6 @@
#include "chunk_index.h"
#include "hypertable_cache.h"
#include "indexing.h"
#include "license.h"
#include "reorder.h"
extern void timescale_reorder_rel(Oid tableOid, Oid indexOid, bool verbose, Oid wait_id,
@ -126,8 +125,6 @@ tsl_move_chunk(PG_FUNCTION_ARGS)
/* used for debugging purposes only see finish_heap_swaps */
Oid wait_id = PG_NARGS() < 6 || PG_ARGISNULL(5) ? InvalidOid : PG_GETARG_OID(5);
license_print_expiration_warning_if_needed();
/*
* Allow move in transactions for testing purposes only
*/

View File

@ -8,18 +8,10 @@
#include <utils/builtins.h>
#include <jsonb_utils.h>
#include "hypertable.h"
#include "license.h"
#include "telemetry/telemetry.h"
#include "dist_util.h"
#include "data_node.h"
#define LICENSE_EDITION_COMMUNITY "community"
#define LICENSE_EDITION_ENTERPRISE "enterprise"
#define LICENSE_KIND_KEY "kind"
#define LICENSE_ID_KEY "id"
#define LICENSE_START_TIME_KEY "start_time"
#define LICENSE_END_TIME_KEY "end_time"
#define DISTRIBUTED_DB_KEY "distributed_db"
#define DISTRIBUTED_MEMBER_KEY "distributed_member"
#define NUM_DISTRIBUTED_HYPERTABLES_KEY "num_distributed_hypertables"
@ -27,26 +19,6 @@
#define NUM_REPLICATED_DISTRIBUTED_HYPERTABLES_KEY "num_replicated_distributed_hypertables"
#define NUM_DATA_NODES_KEY "num_data_nodes"
static void
tsl_telemetry_add_license_info(JsonbParseState *parse_state)
{
if (!license_enterprise_enabled())
ts_jsonb_add_str(parse_state, REQ_LICENSE_EDITION, LICENSE_EDITION_COMMUNITY);
else
{
char *start_time = DatumGetCString(
DirectFunctionCall1(timestamptz_out, TimestampTzGetDatum(license_start_time())));
char *end_time = DatumGetCString(
DirectFunctionCall1(timestamptz_out, TimestampTzGetDatum(license_end_time())));
ts_jsonb_add_str(parse_state, REQ_LICENSE_EDITION, LICENSE_EDITION_ENTERPRISE);
ts_jsonb_add_str(parse_state, LICENSE_KIND_KEY, license_kind_str());
ts_jsonb_add_str(parse_state, LICENSE_ID_KEY, license_id_str());
ts_jsonb_add_str(parse_state, LICENSE_START_TIME_KEY, start_time);
ts_jsonb_add_str(parse_state, LICENSE_END_TIME_KEY, end_time);
}
}
static void
tsl_telemetry_add_distributed_database_info(JsonbParseState *parse_state)
{
@ -81,27 +53,14 @@ tsl_telemetry_add_distributed_database_info(JsonbParseState *parse_state)
void
tsl_telemetry_add_info(JsonbParseState **parse_state)
{
JsonbValue license_key;
JsonbValue distributed_db_key;
/* license */
license_key.type = jbvString;
license_key.val.string.val = REQ_LICENSE_INFO;
license_key.val.string.len = strlen(REQ_LICENSE_INFO);
pushJsonbValue(parse_state, WJB_KEY, &license_key);
/* distributed_db */
distributed_db_key.type = jbvString;
distributed_db_key.val.string.val = DISTRIBUTED_DB_KEY;
distributed_db_key.val.string.len = strlen(DISTRIBUTED_DB_KEY);
pushJsonbValue(parse_state, WJB_KEY, &distributed_db_key);
pushJsonbValue(parse_state, WJB_BEGIN_OBJECT, NULL);
tsl_telemetry_add_license_info(*parse_state);
tsl_telemetry_add_distributed_database_info(*parse_state);
pushJsonbValue(parse_state, WJB_END_OBJECT, NULL);
{
JsonbValue distributed_db_key;
/* distributed_db */
distributed_db_key.type = jbvString;
distributed_db_key.val.string.val = DISTRIBUTED_DB_KEY;
distributed_db_key.val.string.len = strlen(DISTRIBUTED_DB_KEY);
pushJsonbValue(parse_state, WJB_KEY, &distributed_db_key);
pushJsonbValue(parse_state, WJB_BEGIN_OBJECT, NULL);
tsl_telemetry_add_distributed_database_info(*parse_state);
pushJsonbValue(parse_state, WJB_END_OBJECT, NULL);
}
}

View File

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

View File

@ -1,6 +1,6 @@
-- This file and its contents are licensed under the Apache License 2.0.
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.
-- LICENSE-TIMESCALE for a copy of the license.
--------------------------------------------------------------------------
-- show_chunks and drop_chunks functions on a compressed table
-- (issue https://github.com/timescale/timescaledb/issues/1535)

View File

@ -150,7 +150,6 @@ FROM pg_tables WHERE tablespace = 'tablespace1';
\set ON_ERROR_STOP 0
SELECT move_chunk(chunk=>:'COMPRESSED_CHUNK_NAME', destination_tablespace=>'tablespace1', index_destination_tablespace=>'tablespace1', reorder_index=>'_timescaledb_internal."compress_hyper_2_28_chunk__compressed_hypertable_2_b__ts_meta_s"');
WARNING: Timescale License expired
ERROR: cannot directly move internal compression data
\set ON_ERROR_STOP 1
-- ensure that both compressed and uncompressed chunks moved

View File

@ -263,7 +263,6 @@ RESET client_min_messages;
CREATE TABLESPACE tablespace1 OWNER :ROLE_CLUSTER_SUPERUSER LOCATION :TEST_TABLESPACE1_PATH;
\set ON_ERROR_STOP 0
SELECT move_chunk(chunk=>'_timescaledb_internal._dist_hyper_1_4_chunk', destination_tablespace=>'tablespace1', index_destination_tablespace=>'tablespace1', reorder_index=>'disttable_time_idx', verbose=>TRUE);
WARNING: Timescale License expired
ERROR: move_chunk() and reorder_chunk() cannot be used with distributed hypertables
SELECT reorder_chunk('_timescaledb_internal._dist_hyper_1_4_chunk', verbose => TRUE);
ERROR: move_chunk() and reorder_chunk() cannot be used with distributed hypertables

View File

@ -0,0 +1,16 @@
-- 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;
SHOW timescaledb.license;
timescaledb.license
---------------------
timescale
(1 row)
SELECT _timescaledb_internal.tsl_loaded();
tsl_loaded
------------
t
(1 row)

View File

@ -217,7 +217,6 @@ SELECT * FROM test.show_indexes('_timescaledb_internal._hyper_1_2_chunk');
\set ON_ERROR_STOP 0
-- cannot move a chunk with no reorder index on first call
SELECT move_chunk(chunk=>'_timescaledb_internal._hyper_1_2_chunk', destination_tablespace=>'tablespace1', index_destination_tablespace=>'tablespace1');
WARNING: Timescale License expired
ERROR: there is no previously clustered index for table "_hyper_1_2_chunk"
-- cannot move a chunk without a destination tablespace set
SELECT move_chunk(chunk=>'_timescaledb_internal._hyper_1_2_chunk', destination_tablespace=>NULL, index_destination_tablespace=>'tablespace1', reorder_index=>'_timescaledb_internal._hyper_1_2_chunk_cluster_test_time_idx');
@ -249,7 +248,6 @@ END;
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2
-- must be hypertable owner to move a chunk
SELECT move_chunk(chunk=>'_timescaledb_internal._hyper_1_2_chunk', destination_tablespace=>'tablespace2', index_destination_tablespace=>'tablespace2', reorder_index=>'_timescaledb_internal._hyper_1_2_chunk_cluster_test_time_idx', verbose=>TRUE);
WARNING: Timescale License expired
ERROR: must be owner of hypertable "cluster_test"
\set ON_ERROR_STOP 1
-- grant create permissions on tablespace2 so we can use it later
@ -257,7 +255,6 @@ GRANT CREATE ON TABLESPACE tablespace2 TO :ROLE_DEFAULT_PERM_USER;
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER
-- move with chunk index for reorder
SELECT move_chunk(chunk=>'_timescaledb_internal._hyper_1_2_chunk', destination_tablespace=>'tablespace1', index_destination_tablespace=>'tablespace1', reorder_index=>'_timescaledb_internal._hyper_1_2_chunk_cluster_test_time_idx', verbose=>TRUE);
WARNING: Timescale License expired
INFO: reordering "_timescaledb_internal._hyper_1_2_chunk" using sequential scan and sort
INFO: "_hyper_1_2_chunk": found 0 removable, 5 nonremovable row versions in 1 pages
move_chunk

View File

@ -214,7 +214,6 @@ SELECT * FROM test.show_indexes('_timescaledb_internal._hyper_1_2_chunk');
\set ON_ERROR_STOP 0
-- cannot move a chunk with no reorder index on first call
SELECT move_chunk(chunk=>'_timescaledb_internal._hyper_1_2_chunk', destination_tablespace=>'tablespace1', index_destination_tablespace=>'tablespace1');
WARNING: Timescale License expired
ERROR: there is no previously clustered index for table "_hyper_1_2_chunk"
-- cannot move a chunk without a destination tablespace set
SELECT move_chunk(chunk=>'_timescaledb_internal._hyper_1_2_chunk', destination_tablespace=>NULL, index_destination_tablespace=>'tablespace1', reorder_index=>'_timescaledb_internal._hyper_1_2_chunk_cluster_test_time_idx');
@ -246,7 +245,6 @@ END;
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER_2
-- must be hypertable owner to move a chunk
SELECT move_chunk(chunk=>'_timescaledb_internal._hyper_1_2_chunk', destination_tablespace=>'tablespace2', index_destination_tablespace=>'tablespace2', reorder_index=>'_timescaledb_internal._hyper_1_2_chunk_cluster_test_time_idx', verbose=>TRUE);
WARNING: Timescale License expired
ERROR: must be owner of hypertable "cluster_test"
\set ON_ERROR_STOP 1
-- grant create permissions on tablespace2 so we can use it later
@ -254,7 +252,6 @@ GRANT CREATE ON TABLESPACE tablespace2 TO :ROLE_DEFAULT_PERM_USER;
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER
-- move with chunk index for reorder
SELECT move_chunk(chunk=>'_timescaledb_internal._hyper_1_2_chunk', destination_tablespace=>'tablespace1', index_destination_tablespace=>'tablespace1', reorder_index=>'_timescaledb_internal._hyper_1_2_chunk_cluster_test_time_idx', verbose=>TRUE);
WARNING: Timescale License expired
INFO: reordering "_timescaledb_internal._hyper_1_2_chunk" using sequential scan and sort
INFO: "_hyper_1_2_chunk": found 0 removable, 5 nonremovable row versions in 1 pages
move_chunk

View File

@ -1,6 +1,6 @@
-- This file and its contents are licensed under the Apache License 2.0.
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.
-- LICENSE-TIMESCALE for a copy of the license.
--telemetry tests that require a community license
SELECT json_object_field(get_telemetry_report(always_display_report := true)::json,'num_continuous_aggs');
json_object_field

View File

@ -11,7 +11,7 @@ log_line_prefix='%u [%p] '
# This section adds additional options required by TSL
# Note any changes here require updates to appveyor.yml
timescaledb.license_key='E1eyJlbmRfdGltZSI6IjIwMTgtMTAtMDEgKzAwMDAiLCAic3RhcnRfdGltZSI6IjIwMTgtMDktMDEgKzAwMDAiLCAiaWQiOiI0OTBGQjI2MC1BMjkyLTRBRDktOUFBMi0wMzYwODM1NzkxQjgiLCAia2luZCI6InRyaWFsIn0K'
timescaledb.license='timescale'
log_line_prefix='%u [%p] %d '
# PG12 changed the default rounding behavior of floating point
# numbers. Setting extra_float_digits=0 retains the old behavior which

View File

@ -1,131 +0,0 @@
-- 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.
SELECT _timescaledb_internal.tsl_loaded();
tsl_loaded
------------
t
(1 row)
SELECT _timescaledb_internal.enterprise_enabled();
enterprise_enabled
--------------------
t
(1 row)
select * from timescaledb_information.license;
edition | expired | expiration_time
------------+---------+------------------------------
enterprise | t | Sun Sep 30 17:00:00 2018 PDT
(1 row)
-- changing licenses requires superuser privleges
\set ON_ERROR_STOP 0
SET timescaledb.license_key='CommunityLicense';
ERROR: permission denied to set parameter "timescaledb.license_key"
SELECT _timescaledb_internal.current_license_key();
ERROR: must be superuser or a member of pg_read_all_settings to examine the license key
SELECT _timescaledb_internal.tsl_loaded();
tsl_loaded
------------
t
(1 row)
SELECT _timescaledb_internal.enterprise_enabled();
enterprise_enabled
--------------------
t
(1 row)
\set ON_ERROR_STOP 1
\c :TEST_DBNAME :ROLE_SUPERUSER
SELECT _timescaledb_internal.current_license_key();
current_license_key
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
E1eyJlbmRfdGltZSI6IjIwMTgtMTAtMDEgKzAwMDAiLCAic3RhcnRfdGltZSI6IjIwMTgtMDktMDEgKzAwMDAiLCAiaWQiOiI0OTBGQjI2MC1BMjkyLTRBRDktOUFBMi0wMzYwODM1NzkxQjgiLCAia2luZCI6InRyaWFsIn0K
(1 row)
SELECT _timescaledb_internal.tsl_loaded();
tsl_loaded
------------
t
(1 row)
SELECT _timescaledb_internal.enterprise_enabled();
enterprise_enabled
--------------------
t
(1 row)
\set ON_ERROR_STOP 0
SET timescaledb.license_key='ApacheOnly';
ERROR: invalid value for parameter "timescaledb.license_key": "ApacheOnly"
SELECT _timescaledb_internal.current_license_key();
current_license_key
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
E1eyJlbmRfdGltZSI6IjIwMTgtMTAtMDEgKzAwMDAiLCAic3RhcnRfdGltZSI6IjIwMTgtMDktMDEgKzAwMDAiLCAiaWQiOiI0OTBGQjI2MC1BMjkyLTRBRDktOUFBMi0wMzYwODM1NzkxQjgiLCAia2luZCI6InRyaWFsIn0K
(1 row)
SELECT _timescaledb_internal.tsl_loaded();
tsl_loaded
------------
t
(1 row)
SELECT _timescaledb_internal.enterprise_enabled();
enterprise_enabled
--------------------
t
(1 row)
\set ON_ERROR_STOP 1
SET timescaledb.license_key='CommunityLicense';
SELECT _timescaledb_internal.current_license_key();
current_license_key
---------------------
CommunityLicense
(1 row)
SELECT _timescaledb_internal.tsl_loaded();
tsl_loaded
------------
t
(1 row)
SELECT _timescaledb_internal.enterprise_enabled();
enterprise_enabled
--------------------
f
(1 row)
select * from timescaledb_information.license;
edition | expired | expiration_time
-----------+---------+-----------------
community | f | infinity
(1 row)
SET timescaledb.license_key=Default;
SELECT _timescaledb_internal.current_license_key();
current_license_key
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
E1eyJlbmRfdGltZSI6IjIwMTgtMTAtMDEgKzAwMDAiLCAic3RhcnRfdGltZSI6IjIwMTgtMDktMDEgKzAwMDAiLCAiaWQiOiI0OTBGQjI2MC1BMjkyLTRBRDktOUFBMi0wMzYwODM1NzkxQjgiLCAia2luZCI6InRyaWFsIn0K
(1 row)
SELECT _timescaledb_internal.tsl_loaded();
tsl_loaded
------------
t
(1 row)
SELECT _timescaledb_internal.enterprise_enabled();
enterprise_enabled
--------------------
t
(1 row)
select * from timescaledb_information.license;
edition | expired | expiration_time
------------+---------+------------------------------
enterprise | t | Sun Sep 30 17:00:00 2018 PDT
(1 row)

View File

@ -2,7 +2,6 @@ set(TEST_FILES_SHARED
constraint_exclusion_prepared.sql
decompress_placeholdervar.sql
ordered_append_join.sql
license.sql
)
set(TEST_TEMPLATES_SHARED

View File

@ -1,40 +0,0 @@
-- 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.
SELECT _timescaledb_internal.tsl_loaded();
SELECT _timescaledb_internal.enterprise_enabled();
select * from timescaledb_information.license;
-- changing licenses requires superuser privleges
\set ON_ERROR_STOP 0
SET timescaledb.license_key='CommunityLicense';
SELECT _timescaledb_internal.current_license_key();
SELECT _timescaledb_internal.tsl_loaded();
SELECT _timescaledb_internal.enterprise_enabled();
\set ON_ERROR_STOP 1
\c :TEST_DBNAME :ROLE_SUPERUSER
SELECT _timescaledb_internal.current_license_key();
SELECT _timescaledb_internal.tsl_loaded();
SELECT _timescaledb_internal.enterprise_enabled();
\set ON_ERROR_STOP 0
SET timescaledb.license_key='ApacheOnly';
SELECT _timescaledb_internal.current_license_key();
SELECT _timescaledb_internal.tsl_loaded();
SELECT _timescaledb_internal.enterprise_enabled();
\set ON_ERROR_STOP 1
SET timescaledb.license_key='CommunityLicense';
SELECT _timescaledb_internal.current_license_key();
SELECT _timescaledb_internal.tsl_loaded();
SELECT _timescaledb_internal.enterprise_enabled();
select * from timescaledb_information.license;
SET timescaledb.license_key=Default;
SELECT _timescaledb_internal.current_license_key();
SELECT _timescaledb_internal.tsl_loaded();
SELECT _timescaledb_internal.enterprise_enabled();
select * from timescaledb_information.license;

View File

@ -15,8 +15,10 @@ set(TEST_FILES
)
set(TEST_FILES_DEBUG
bgw_db_scheduler.sql
bgw_reorder_drop_chunks.sql
compress_bgw_reorder_drop_chunks.sql
chunk_utils_compression.sql
compression_algos.sql
compression_ddl.sql
compression_errors.sql
@ -55,9 +57,11 @@ set(TEST_FILES_DEBUG
remote_txn_id.sql
remote_txn_resolve.sql
remote_txn.sql
telemetry_community.sql
telemetry_distributed.sql
transparent_decompression_queries.sql
tsl_tables.sql
license.sql
)
set(TEST_TEMPLATES
@ -109,6 +113,7 @@ endif()
# dist_views.sql sets some global information, so do not run it
# in parallel
set(SOLO_TESTS
bgw_db_scheduler
bgw_reorder_drop_chunks
chunk_api
compress_bgw_reorder_drop_chunks

View File

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

View File

@ -1,6 +1,6 @@
-- This file and its contents are licensed under the Apache License 2.0.
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.
-- LICENSE-TIMESCALE for a copy of the license.
--------------------------------------------------------------------------
-- show_chunks and drop_chunks functions on a compressed table

8
tsl/test/sql/license.sql Normal file
View File

@ -0,0 +1,8 @@
-- 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;
SHOW timescaledb.license;
SELECT _timescaledb_internal.tsl_loaded();

View File

@ -1,6 +1,6 @@
-- This file and its contents are licensed under the Apache License 2.0.
-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.
-- LICENSE-TIMESCALE for a copy of the license.
--telemetry tests that require a community license