Merge pull request #12897 from zmstone/0418-support-dynamic-license
feat(license): add business-critical customer type
This commit is contained in:
commit
307cd79be2
|
@ -31,6 +31,7 @@
|
||||||
-define(SMALL_CUSTOMER, 0).
|
-define(SMALL_CUSTOMER, 0).
|
||||||
-define(MEDIUM_CUSTOMER, 1).
|
-define(MEDIUM_CUSTOMER, 1).
|
||||||
-define(LARGE_CUSTOMER, 2).
|
-define(LARGE_CUSTOMER, 2).
|
||||||
|
-define(BUSINESS_CRITICAL_CUSTOMER, 3).
|
||||||
-define(EVALUATION_CUSTOMER, 10).
|
-define(EVALUATION_CUSTOMER, 10).
|
||||||
|
|
||||||
-define(EXPIRED_DAY, -90).
|
-define(EXPIRED_DAY, -90).
|
||||||
|
|
|
@ -154,7 +154,16 @@ do_update({key, Content}, Conf) when is_binary(Content); is_list(Content) ->
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
erlang:throw(Reason)
|
erlang:throw(Reason)
|
||||||
end;
|
end;
|
||||||
do_update({setting, Setting}, Conf) ->
|
do_update({setting, Setting0}, Conf) ->
|
||||||
|
#{<<"key">> := Key} = Conf,
|
||||||
|
%% only allow updating dynamic_max_connections when it's BUSINESS_CRITICAL
|
||||||
|
Setting =
|
||||||
|
case emqx_license_parser:is_business_critical(Key) of
|
||||||
|
true ->
|
||||||
|
Setting0;
|
||||||
|
false ->
|
||||||
|
maps:without([<<"dynamic_max_connections">>], Setting0)
|
||||||
|
end,
|
||||||
maps:merge(Conf, Setting);
|
maps:merge(Conf, Setting);
|
||||||
do_update(NewConf, _PrevConf) ->
|
do_update(NewConf, _PrevConf) ->
|
||||||
#{<<"key">> := NewKey} = NewConf,
|
#{<<"key">> := NewKey} = NewConf,
|
||||||
|
|
|
@ -33,7 +33,9 @@
|
||||||
expiry_epoch/0,
|
expiry_epoch/0,
|
||||||
purge/0,
|
purge/0,
|
||||||
limits/0,
|
limits/0,
|
||||||
print_warnings/1
|
print_warnings/1,
|
||||||
|
get_max_connections/1,
|
||||||
|
get_dynamic_max_connections/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
|
@ -46,21 +48,23 @@
|
||||||
|
|
||||||
-define(LICENSE_TAB, emqx_license).
|
-define(LICENSE_TAB, emqx_license).
|
||||||
|
|
||||||
|
-type limits() :: #{max_connections := non_neg_integer() | ?ERR_EXPIRED}.
|
||||||
|
-type license() :: emqx_license_parser:license().
|
||||||
|
-type fetcher() :: fun(() -> {ok, license()} | {error, term()}).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% API
|
%% API
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
-type limits() :: #{max_connections := non_neg_integer() | ?ERR_EXPIRED}.
|
-spec start_link(fetcher()) -> {ok, pid()}.
|
||||||
|
|
||||||
-spec start_link(emqx_license_parser:license()) -> {ok, pid()}.
|
|
||||||
start_link(LicenseFetcher) ->
|
start_link(LicenseFetcher) ->
|
||||||
start_link(LicenseFetcher, ?CHECK_INTERVAL).
|
start_link(LicenseFetcher, ?CHECK_INTERVAL).
|
||||||
|
|
||||||
-spec start_link(emqx_license_parser:license(), timeout()) -> {ok, pid()}.
|
-spec start_link(fetcher(), timeout()) -> {ok, pid()}.
|
||||||
start_link(LicenseFetcher, CheckInterval) ->
|
start_link(LicenseFetcher, CheckInterval) ->
|
||||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [LicenseFetcher, CheckInterval], []).
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [LicenseFetcher, CheckInterval], []).
|
||||||
|
|
||||||
-spec update(emqx_license_parser:license()) -> map().
|
-spec update(license()) -> map().
|
||||||
update(License) ->
|
update(License) ->
|
||||||
gen_server:call(?MODULE, {update, License}, infinity).
|
gen_server:call(?MODULE, {update, License}, infinity).
|
||||||
|
|
||||||
|
@ -210,8 +214,7 @@ check_license(License) ->
|
||||||
DaysLeft = days_left(License),
|
DaysLeft = days_left(License),
|
||||||
IsOverdue = is_overdue(License, DaysLeft),
|
IsOverdue = is_overdue(License, DaysLeft),
|
||||||
NeedRestriction = IsOverdue,
|
NeedRestriction = IsOverdue,
|
||||||
MaxConn = emqx_license_parser:max_connections(License),
|
#{max_connections := MaxConn} = Limits = limits(License, NeedRestriction),
|
||||||
Limits = limits(License, NeedRestriction),
|
|
||||||
true = apply_limits(Limits),
|
true = apply_limits(Limits),
|
||||||
#{
|
#{
|
||||||
warn_evaluation => warn_evaluation(License, NeedRestriction, MaxConn),
|
warn_evaluation => warn_evaluation(License, NeedRestriction, MaxConn),
|
||||||
|
@ -223,8 +226,34 @@ warn_evaluation(License, false, MaxConn) ->
|
||||||
warn_evaluation(_License, _NeedRestrict, _Limits) ->
|
warn_evaluation(_License, _NeedRestrict, _Limits) ->
|
||||||
false.
|
false.
|
||||||
|
|
||||||
limits(License, false) -> #{max_connections => emqx_license_parser:max_connections(License)};
|
limits(License, false) ->
|
||||||
limits(_License, true) -> #{max_connections => ?ERR_EXPIRED}.
|
#{
|
||||||
|
max_connections => get_max_connections(License)
|
||||||
|
};
|
||||||
|
limits(_License, true) ->
|
||||||
|
#{
|
||||||
|
max_connections => ?ERR_EXPIRED
|
||||||
|
}.
|
||||||
|
|
||||||
|
%% @doc Return the max_connections limit defined in license.
|
||||||
|
%% For business-critical type, it returns the dynamic value set in config.
|
||||||
|
-spec get_max_connections(license()) -> non_neg_integer().
|
||||||
|
get_max_connections(License) ->
|
||||||
|
Max = emqx_license_parser:max_connections(License),
|
||||||
|
Dyn =
|
||||||
|
case emqx_license_parser:customer_type(License) of
|
||||||
|
?BUSINESS_CRITICAL_CUSTOMER ->
|
||||||
|
min(get_dynamic_max_connections(), Max);
|
||||||
|
_ ->
|
||||||
|
Max
|
||||||
|
end,
|
||||||
|
min(Max, Dyn).
|
||||||
|
|
||||||
|
%% @doc Get the dynamic max_connections limit set in config.
|
||||||
|
%% It's only meaningful for business-critical license.
|
||||||
|
-spec get_dynamic_max_connections() -> non_neg_integer().
|
||||||
|
get_dynamic_max_connections() ->
|
||||||
|
emqx_conf:get([license, dynamic_max_connections]).
|
||||||
|
|
||||||
days_left(License) ->
|
days_left(License) ->
|
||||||
DateEnd = emqx_license_parser:expiry_date(License),
|
DateEnd = emqx_license_parser:expiry_date(License),
|
||||||
|
|
|
@ -147,7 +147,7 @@ error_msg(Code, Msg) ->
|
||||||
{400, error_msg(?BAD_REQUEST, <<"Invalid request params">>)}.
|
{400, error_msg(?BAD_REQUEST, <<"Invalid request params">>)}.
|
||||||
|
|
||||||
'/license/setting'(get, _Params) ->
|
'/license/setting'(get, _Params) ->
|
||||||
{200, maps:remove(<<"key">>, emqx_config:get_raw([license]))};
|
{200, get_setting()};
|
||||||
'/license/setting'(put, #{body := Setting}) ->
|
'/license/setting'(put, #{body := Setting}) ->
|
||||||
case emqx_license:update_setting(Setting) of
|
case emqx_license:update_setting(Setting) of
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
|
@ -170,3 +170,14 @@ fields(key_license) ->
|
||||||
|
|
||||||
setting() ->
|
setting() ->
|
||||||
lists:keydelete(key, 1, emqx_license_schema:fields(key_license)).
|
lists:keydelete(key, 1, emqx_license_schema:fields(key_license)).
|
||||||
|
|
||||||
|
%% Drop dynamic_max_connections unless it's a BUSINESS_CRITICAL license.
|
||||||
|
get_setting() ->
|
||||||
|
#{<<"key">> := Key} = Raw = emqx_config:get_raw([license]),
|
||||||
|
Result = maps:remove(<<"key">>, Raw),
|
||||||
|
case emqx_license_parser:is_business_critical(Key) of
|
||||||
|
true ->
|
||||||
|
Result;
|
||||||
|
false ->
|
||||||
|
maps:remove(<<"dynamic_max_connections">>, Result)
|
||||||
|
end.
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
?SMALL_CUSTOMER
|
?SMALL_CUSTOMER
|
||||||
| ?MEDIUM_CUSTOMER
|
| ?MEDIUM_CUSTOMER
|
||||||
| ?LARGE_CUSTOMER
|
| ?LARGE_CUSTOMER
|
||||||
|
| ?BUSINESS_CRITICAL_CUSTOMER
|
||||||
| ?EVALUATION_CUSTOMER.
|
| ?EVALUATION_CUSTOMER.
|
||||||
|
|
||||||
-type license_type() :: ?OFFICIAL | ?TRIAL.
|
-type license_type() :: ?OFFICIAL | ?TRIAL.
|
||||||
|
@ -41,6 +42,8 @@
|
||||||
source := binary()
|
source := binary()
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
-type raw_license() :: string() | binary() | default.
|
||||||
|
|
||||||
-export_type([
|
-export_type([
|
||||||
license_data/0,
|
license_data/0,
|
||||||
customer_type/0,
|
customer_type/0,
|
||||||
|
@ -56,7 +59,8 @@
|
||||||
customer_type/1,
|
customer_type/1,
|
||||||
license_type/1,
|
license_type/1,
|
||||||
expiry_date/1,
|
expiry_date/1,
|
||||||
max_connections/1
|
max_connections/1,
|
||||||
|
is_business_critical/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% for testing purpose
|
%% for testing purpose
|
||||||
|
@ -94,7 +98,7 @@ default() -> emqx_license_schema:default_license().
|
||||||
%% @doc Parse license key.
|
%% @doc Parse license key.
|
||||||
%% If the license key is prefixed with "file://path/to/license/file",
|
%% If the license key is prefixed with "file://path/to/license/file",
|
||||||
%% then the license key is read from the file.
|
%% then the license key is read from the file.
|
||||||
-spec parse(default | string() | binary()) -> {ok, license()} | {error, map()}.
|
-spec parse(raw_license()) -> {ok, license()} | {error, map()}.
|
||||||
parse(Content) ->
|
parse(Content) ->
|
||||||
parse(to_bin(Content), ?MODULE:pubkey()).
|
parse(to_bin(Content), ?MODULE:pubkey()).
|
||||||
|
|
||||||
|
@ -146,6 +150,13 @@ expiry_date(#{module := Module, data := LicenseData}) ->
|
||||||
max_connections(#{module := Module, data := LicenseData}) ->
|
max_connections(#{module := Module, data := LicenseData}) ->
|
||||||
Module:max_connections(LicenseData).
|
Module:max_connections(LicenseData).
|
||||||
|
|
||||||
|
-spec is_business_critical(license() | raw_license()) -> boolean().
|
||||||
|
is_business_critical(#{module := Module, data := LicenseData}) ->
|
||||||
|
Module:customer_type(LicenseData) =:= ?BUSINESS_CRITICAL_CUSTOMER;
|
||||||
|
is_business_critical(Key) when is_binary(Key) ->
|
||||||
|
{ok, License} = parse(Key),
|
||||||
|
is_business_critical(License).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Private functions
|
%% Private functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
-export([namespace/0, roots/0, fields/1, validations/0, desc/1, tags/0]).
|
-export([namespace/0, roots/0, fields/1, validations/0, desc/1, tags/0]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
default_license/0
|
default_license/0,
|
||||||
|
default_setting/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
namespace() -> "license".
|
namespace() -> "license".
|
||||||
|
@ -45,16 +46,26 @@ fields(key_license) ->
|
||||||
required => true,
|
required => true,
|
||||||
desc => ?DESC(key_field)
|
desc => ?DESC(key_field)
|
||||||
}},
|
}},
|
||||||
|
%% This feature is not made GA yet, hence hidden.
|
||||||
|
%% When license is issued to cutomer-type BUSINESS_CRITICAL (code 3)
|
||||||
|
%% This config is taken as the real max_connections limit.
|
||||||
|
{dynamic_max_connections, #{
|
||||||
|
type => non_neg_integer(),
|
||||||
|
default => default(dynamic_max_connections),
|
||||||
|
required => false,
|
||||||
|
importance => ?IMPORTANCE_HIDDEN,
|
||||||
|
desc => ?DESC(dynamic_max_connections)
|
||||||
|
}},
|
||||||
{connection_low_watermark, #{
|
{connection_low_watermark, #{
|
||||||
type => emqx_schema:percent(),
|
type => emqx_schema:percent(),
|
||||||
default => <<"75%">>,
|
default => default(connection_low_watermark),
|
||||||
example => <<"75%">>,
|
example => default(connection_low_watermark),
|
||||||
desc => ?DESC(connection_low_watermark_field)
|
desc => ?DESC(connection_low_watermark_field)
|
||||||
}},
|
}},
|
||||||
{connection_high_watermark, #{
|
{connection_high_watermark, #{
|
||||||
type => emqx_schema:percent(),
|
type => emqx_schema:percent(),
|
||||||
default => <<"80%">>,
|
default => default(connection_high_watermark),
|
||||||
example => <<"80%">>,
|
example => default(connection_high_watermark),
|
||||||
desc => ?DESC(connection_high_watermark_field)
|
desc => ?DESC(connection_high_watermark_field)
|
||||||
}}
|
}}
|
||||||
].
|
].
|
||||||
|
@ -87,11 +98,39 @@ check_license_watermark(Conf) ->
|
||||||
|
|
||||||
%% @doc The default license key.
|
%% @doc The default license key.
|
||||||
%% This default license has 25 connections limit.
|
%% This default license has 25 connections limit.
|
||||||
%% Issued on 2023-12-08 and valid for 5 years (1825 days)
|
%% Issued on 2024-04-18 and valid for 5 years (1825 days)
|
||||||
%% NOTE: when updating a new key, the schema doc in emqx_license_schema.hocon
|
%%
|
||||||
%% should be updated accordingly
|
%% NOTE: when updating a new key, below should be updated accordingly:
|
||||||
|
%% - emqx_license_schema.hocon default connections limit
|
||||||
|
%% - default(dynamic_max_connections) return value
|
||||||
default_license() ->
|
default_license() ->
|
||||||
<<
|
<<
|
||||||
"MjIwMTExCjAKMTAKRXZhbHVhdGlvbgpjb250YWN0QGVtcXguaW8KdHJpYWwKMjAyMzEyMDgKMTgyNQoyNQo=."
|
"MjIwMTExCjAKMTAKRXZhbHVhdGlvbgpjb250YWN0QGVtcXguaW8KdHJpYWwKMjAyNDA0MTgKMTgyNQoyNQo="
|
||||||
"MEUCIE271MtH+4bb39OZKD4mvVkurwZ3LX44KUvuOxkbjQz2AiEAqL7BP44PMUS5z5SAN1M4y3v3h47J8qORAqcuetnyexw="
|
"."
|
||||||
|
"MEUCICMWWkfrvyMwQaQAOXEsEcs+d6+5uXc1BDxR7j25fRy4AiEAmblQ4p+FFmdsvnKgcRRkv1zj7PExmZKVk3mVcxH3fgw="
|
||||||
>>.
|
>>.
|
||||||
|
|
||||||
|
%% @doc Exported for testing
|
||||||
|
default_setting() ->
|
||||||
|
Keys =
|
||||||
|
[
|
||||||
|
connection_low_watermark,
|
||||||
|
connection_high_watermark,
|
||||||
|
dynamic_max_connections
|
||||||
|
],
|
||||||
|
maps:from_list(
|
||||||
|
lists:map(
|
||||||
|
fun(K) ->
|
||||||
|
{K, default(K)}
|
||||||
|
end,
|
||||||
|
Keys
|
||||||
|
)
|
||||||
|
).
|
||||||
|
|
||||||
|
default(connection_low_watermark) ->
|
||||||
|
<<"75%">>;
|
||||||
|
default(connection_high_watermark) ->
|
||||||
|
<<"80%">>;
|
||||||
|
default(dynamic_max_connections) ->
|
||||||
|
%Must match the value encoded in default license.
|
||||||
|
25.
|
||||||
|
|
|
@ -65,6 +65,7 @@ t_conf_update(_Config) ->
|
||||||
#{
|
#{
|
||||||
connection_high_watermark => 0.5,
|
connection_high_watermark => 0.5,
|
||||||
connection_low_watermark => 0.45,
|
connection_low_watermark => 0.45,
|
||||||
|
dynamic_max_connections => 25,
|
||||||
key => LicenseKey
|
key => LicenseKey
|
||||||
},
|
},
|
||||||
emqx:get_config([license])
|
emqx:get_config([license])
|
||||||
|
|
|
@ -19,17 +19,16 @@ all() ->
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
emqx_license_test_lib:mock_parser(),
|
emqx_license_test_lib:mock_parser(),
|
||||||
|
Setting = emqx_license_schema:default_setting(),
|
||||||
|
Key = emqx_license_test_lib:make_license(#{max_connections => "100"}),
|
||||||
|
LicenseConf = maps:merge(#{key => Key}, Setting),
|
||||||
Apps = emqx_cth_suite:start(
|
Apps = emqx_cth_suite:start(
|
||||||
[
|
[
|
||||||
emqx,
|
emqx,
|
||||||
emqx_conf,
|
emqx_conf,
|
||||||
{emqx_license, #{
|
{emqx_license, #{
|
||||||
config => #{
|
config => #{
|
||||||
license => #{
|
license => LicenseConf
|
||||||
key => emqx_license_test_lib:make_license(#{max_connections => "100"}),
|
|
||||||
connection_low_watermark => <<"75%">>,
|
|
||||||
connection_high_watermark => <<"80%">>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
{emqx_dashboard,
|
{emqx_dashboard,
|
||||||
|
@ -50,7 +49,7 @@ init_per_testcase(_TestCase, Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_testcase(_TestCase, _Config) ->
|
end_per_testcase(_TestCase, _Config) ->
|
||||||
{ok, _} = reset_license(),
|
ok = reset_license(),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
@ -70,7 +69,11 @@ default_license() ->
|
||||||
emqx_license_test_lib:make_license(#{max_connections => "100"}).
|
emqx_license_test_lib:make_license(#{max_connections => "100"}).
|
||||||
|
|
||||||
reset_license() ->
|
reset_license() ->
|
||||||
emqx_license:update_key(default_license()).
|
{ok, _} = emqx_license:update_key(default_license()),
|
||||||
|
Setting = emqx_license_schema:default_setting(),
|
||||||
|
Req = maps:from_list([{atom_to_binary(K), V} || {K, V} <- maps:to_list(Setting)]),
|
||||||
|
{ok, _} = emqx_license:update_setting(Req),
|
||||||
|
ok.
|
||||||
|
|
||||||
assert_untouched_license() ->
|
assert_untouched_license() ->
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
|
@ -224,6 +227,26 @@ t_license_setting(_Config) ->
|
||||||
),
|
),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_license_setting_bc(_Config) ->
|
||||||
|
%% Create a BC license
|
||||||
|
Key = emqx_license_test_lib:make_license(#{customer_type => "3"}),
|
||||||
|
Res = request(post, uri(["license"]), #{key => Key}),
|
||||||
|
?assertMatch({ok, 200, _}, Res),
|
||||||
|
%% get
|
||||||
|
GetRes = request(get, uri(["license", "setting"]), []),
|
||||||
|
validate_setting(GetRes, <<"75%">>, <<"80%">>, 25),
|
||||||
|
%% update
|
||||||
|
Low = <<"50%">>,
|
||||||
|
High = <<"55%">>,
|
||||||
|
UpdateRes = request(put, uri(["license", "setting"]), #{
|
||||||
|
<<"connection_low_watermark">> => Low,
|
||||||
|
<<"connection_high_watermark">> => High,
|
||||||
|
<<"dynamic_max_connections">> => 26
|
||||||
|
}),
|
||||||
|
validate_setting(UpdateRes, Low, High, 26),
|
||||||
|
?assertEqual(26, emqx_config:get([license, dynamic_max_connections])),
|
||||||
|
ok.
|
||||||
|
|
||||||
validate_setting(Res, ExpectLow, ExpectHigh) ->
|
validate_setting(Res, ExpectLow, ExpectHigh) ->
|
||||||
?assertMatch({ok, 200, _}, Res),
|
?assertMatch({ok, 200, _}, Res),
|
||||||
{ok, 200, Payload} = Res,
|
{ok, 200, Payload} = Res,
|
||||||
|
@ -234,3 +257,13 @@ validate_setting(Res, ExpectLow, ExpectHigh) ->
|
||||||
},
|
},
|
||||||
emqx_utils_json:decode(Payload, [return_maps])
|
emqx_utils_json:decode(Payload, [return_maps])
|
||||||
).
|
).
|
||||||
|
|
||||||
|
validate_setting(Res, ExpectLow, ExpectHigh, DynMax) ->
|
||||||
|
?assertMatch({ok, 200, _}, Res),
|
||||||
|
{ok, 200, Payload} = Res,
|
||||||
|
#{
|
||||||
|
<<"connection_low_watermark">> := ExpectLow,
|
||||||
|
<<"connection_high_watermark">> := ExpectHigh,
|
||||||
|
<<"dynamic_max_connections">> := DynMax
|
||||||
|
} =
|
||||||
|
emqx_utils_json:decode(Payload, [return_maps]).
|
||||||
|
|
|
@ -12,17 +12,12 @@ connection_low_watermark_field.desc:
|
||||||
connection_low_watermark_field.label:
|
connection_low_watermark_field.label:
|
||||||
"""Connection low watermark"""
|
"""Connection low watermark"""
|
||||||
|
|
||||||
connection_high_watermark_field_deprecated.desc:
|
dynamic_max_connections {
|
||||||
"""deprecated use /license/setting instead"""
|
label: "Dynamic Connections Limit"
|
||||||
|
desc: """~
|
||||||
connection_high_watermark_field_deprecated.label:
|
Only applicable for "Business Critical" license type. This config sets the current allocation of license for the current cluster.
|
||||||
"""deprecated use /license/setting instead"""
|
This value cannot exceed the connections limit assigned in the license key."""
|
||||||
|
}
|
||||||
connection_low_watermark_field_deprecated.desc:
|
|
||||||
"""deprecated use /license/setting instead"""
|
|
||||||
|
|
||||||
connection_low_watermark_field_deprecated.label:
|
|
||||||
"""deprecated use /license/setting instead"""
|
|
||||||
|
|
||||||
key_field.desc:
|
key_field.desc:
|
||||||
"""This configuration parameter is designated for the license key and supports below input formats:
|
"""This configuration parameter is designated for the license key and supports below input formats:
|
||||||
|
@ -43,7 +38,7 @@ license_root.desc:
|
||||||
"""Defines the EMQX Enterprise license.
|
"""Defines the EMQX Enterprise license.
|
||||||
|
|
||||||
EMQX Enterprise is initially provided with a default trial license.
|
EMQX Enterprise is initially provided with a default trial license.
|
||||||
This license, issued in December 2023, is valid for a period of 5 years.
|
This license, issued in April 2024, is valid for a period of 5 years.
|
||||||
It supports up to 25 concurrent connections, catering to early-stage development and testing needs.
|
It supports up to 25 concurrent connections, catering to early-stage development and testing needs.
|
||||||
|
|
||||||
For deploying EMQX Enterprise in a production environment, a different license is required. You can apply for a production license by visiting https://www.emqx.com/apply-licenses/emqx?version=5"""
|
For deploying EMQX Enterprise in a production environment, a different license is required. You can apply for a production license by visiting https://www.emqx.com/apply-licenses/emqx?version=5"""
|
||||||
|
|
Loading…
Reference in New Issue