style(emqx_license): erlfmt refomrat apps/emqx_license

This commit is contained in:
Zaiming (Stone) Shi 2022-03-25 07:13:08 +01:00
parent 2e05e45245
commit 4f396cceb8
19 changed files with 762 additions and 557 deletions

View File

@ -1 +1,3 @@
{deps, [{emqx, {path, "../../apps/emqx"}}]}. {deps, [{emqx, {path, "../../apps/emqx"}}]}.
{project_plugins, [erlfmt]}.

View File

@ -1,7 +1,8 @@
{application,emqx_license, {application, emqx_license, [
[{description,"EMQX License"}, {description, "EMQX License"},
{vsn, "5.0.0"}, {vsn, "5.0.0"},
{modules, []}, {modules, []},
{registered, [emqx_license_sup]}, {registered, [emqx_license_sup]},
{applications, [kernel, stdlib]}, {applications, [kernel, stdlib]},
{mod,{emqx_license_app,[]}}]}. {mod, {emqx_license_app, []}}
]}.

View File

@ -10,17 +10,20 @@
-behaviour(emqx_config_handler). -behaviour(emqx_config_handler).
-export([pre_config_update/3, -export([
pre_config_update/3,
post_config_update/5 post_config_update/5
]). ]).
-export([load/0, -export([
load/0,
check/2, check/2,
unload/0, unload/0,
read_license/0, read_license/0,
read_license/1, read_license/1,
update_file/1, update_file/1,
update_key/1]). update_key/1
]).
-define(CONF_KEY_PATH, [license]). -define(CONF_KEY_PATH, [license]).
@ -52,7 +55,8 @@ update_file(Filename) when is_binary(Filename); is_list(Filename) ->
Result = emqx_conf:update( Result = emqx_conf:update(
?CONF_KEY_PATH, ?CONF_KEY_PATH,
{file, Filename}, {file, Filename},
#{rawconf_with_defaults => true, override_to => local}), #{rawconf_with_defaults => true, override_to => local}
),
handle_config_update_result(Result). handle_config_update_result(Result).
-spec update_key(binary() | string()) -> -spec update_key(binary() | string()) ->
@ -61,7 +65,8 @@ update_key(Value) when is_binary(Value); is_list(Value) ->
Result = emqx_conf:update( Result = emqx_conf:update(
?CONF_KEY_PATH, ?CONF_KEY_PATH,
{key, Value}, {key, Value},
#{rawconf_with_defaults => true, override_to => cluster}), #{rawconf_with_defaults => true, override_to => cluster}
),
handle_config_update_result(Result). handle_config_update_result(Result).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -82,8 +87,10 @@ check(_ConnInfo, AckProps) ->
{ok, AckProps} {ok, AckProps}
end; end;
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{msg => "connection_rejected_due_to_license_not_loaded", ?SLOG(error, #{
reason => Reason}), msg => "connection_rejected_due_to_license_not_loaded",
reason => Reason
}),
{stop, {error, ?RC_QUOTA_EXCEEDED}} {stop, {error, ?RC_QUOTA_EXCEEDED}}
end. end.
@ -98,7 +105,8 @@ post_config_update(_Path, _Cmd, NewConf, _Old, _AppEnvs) ->
case read_license(NewConf) of case read_license(NewConf) of
{ok, License} -> {ok, License} ->
{ok, emqx_license_checker:update(License)}; {ok, emqx_license_checker:update(License)};
{error, _} = Error -> Error {error, _} = Error ->
Error
end. end.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -124,7 +132,6 @@ do_update({file, Filename}, Conf) ->
{error, Reason} -> {error, Reason} ->
erlang:throw({invalid_license_file, Reason}) erlang:throw({invalid_license_file, Reason})
end; end;
do_update({key, Content}, Conf) when is_binary(Content); is_list(Content) -> do_update({key, Content}, Conf) when is_binary(Content); is_list(Content) ->
case emqx_license_parser:parse(Content) of case emqx_license_parser:parse(Content) of
{ok, _License} -> {ok, _License} ->
@ -144,9 +151,10 @@ read_license(#{file := Filename}) ->
{ok, Content} -> emqx_license_parser:parse(Content); {ok, Content} -> emqx_license_parser:parse(Content);
{error, _} = Error -> Error {error, _} = Error -> Error
end; end;
read_license(#{key := Content}) -> read_license(#{key := Content}) ->
emqx_license_parser:parse(Content). emqx_license_parser:parse(Content).
handle_config_update_result({error, _} = Error) -> Error; handle_config_update_result({error, _} = Error) ->
handle_config_update_result({ok, #{post_config_update := #{emqx_license := Result}}}) -> {ok, Result}. Error;
handle_config_update_result({ok, #{post_config_update := #{emqx_license := Result}}}) ->
{ok, Result}.

View File

@ -12,18 +12,22 @@
-define(CHECK_INTERVAL, 5000). -define(CHECK_INTERVAL, 5000).
-define(EXPIRY_ALARM_CHECK_INTERVAL, 24 * 60 * 60). -define(EXPIRY_ALARM_CHECK_INTERVAL, 24 * 60 * 60).
-export([start_link/1, -export([
start_link/1,
start_link/2, start_link/2,
update/1, update/1,
dump/0, dump/0,
purge/0, purge/0,
limits/0]). limits/0
]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, -export([
init/1,
handle_call/3, handle_call/3,
handle_cast/2, handle_cast/2,
handle_info/2]). handle_info/2
]).
-define(LICENSE_TAB, emqx_license). -define(LICENSE_TAB, emqx_license).
@ -71,10 +75,14 @@ purge() ->
init([LicenseFetcher, CheckInterval]) -> init([LicenseFetcher, CheckInterval]) ->
case LicenseFetcher() of case LicenseFetcher() of
{ok, License} -> {ok, License} ->
?LICENSE_TAB = ets:new(?LICENSE_TAB, [set, protected, named_table, {read_concurrency, true}]), ?LICENSE_TAB = ets:new(?LICENSE_TAB, [
set, protected, named_table, {read_concurrency, true}
]),
#{} = check_license(License), #{} = check_license(License),
State0 = ensure_check_license_timer(#{check_license_interval => CheckInterval, State0 = ensure_check_license_timer(#{
license => License}), check_license_interval => CheckInterval,
license => License
}),
State = ensure_check_expiry_timer(State0), State = ensure_check_expiry_timer(State0),
{ok, State}; {ok, State};
{error, Reason} -> {error, Reason} ->
@ -100,12 +108,10 @@ handle_info(check_license, #{license := License} = State) ->
NewState = ensure_check_license_timer(State), NewState = ensure_check_license_timer(State),
?tp(debug, emqx_license_checked, #{}), ?tp(debug, emqx_license_checked, #{}),
{noreply, NewState}; {noreply, NewState};
handle_info(check_expiry_alarm, #{license := License} = State) -> handle_info(check_expiry_alarm, #{license := License} = State) ->
_ = expiry_early_alarm(License), _ = expiry_early_alarm(License),
NewState = ensure_check_expiry_timer(State), NewState = ensure_check_expiry_timer(State),
{noreply, NewState}; {noreply, NewState};
handle_info(_Msg, State) -> handle_info(_Msg, State) ->
{noreply, State}. {noreply, State}.
@ -123,7 +129,8 @@ ensure_check_expiry_timer(State) ->
State#{expiry_alarm_timer => Ref}. State#{expiry_alarm_timer => Ref}.
cancel_timer(State, Key) -> cancel_timer(State, Key) ->
_ = case maps:find(Key, State) of _ =
case maps:find(Key, State) of
{ok, Ref} when is_reference(Ref) -> erlang:cancel_timer(Ref); {ok, Ref} when is_reference(Ref) -> erlang:cancel_timer(Ref);
_ -> ok _ -> ok
end, end,
@ -134,12 +141,15 @@ check_license(License) ->
NeedRestrict = need_restrict(License, DaysLeft), NeedRestrict = need_restrict(License, DaysLeft),
Limits = limits(License, NeedRestrict), Limits = limits(License, NeedRestrict),
true = apply_limits(Limits), true = apply_limits(Limits),
#{warn_evaluation => warn_evaluation(License, NeedRestrict), #{
warn_expiry => warn_expiry(License, NeedRestrict)}. warn_evaluation => warn_evaluation(License, NeedRestrict),
warn_expiry => warn_expiry(License, NeedRestrict)
}.
warn_evaluation(License, false) -> warn_evaluation(License, false) ->
emqx_license_parser:customer_type(License) == ?EVALUATION_CUSTOMER; emqx_license_parser:customer_type(License) == ?EVALUATION_CUSTOMER;
warn_evaluation(_License, _NeedRestrict) -> false. warn_evaluation(_License, _NeedRestrict) ->
false.
warn_expiry(_License, NeedRestrict) -> NeedRestrict. warn_expiry(_License, NeedRestrict) -> NeedRestrict.
@ -155,12 +165,15 @@ need_restrict(License, DaysLeft)->
CType = emqx_license_parser:customer_type(License), CType = emqx_license_parser:customer_type(License),
Type = emqx_license_parser:license_type(License), Type = emqx_license_parser:license_type(License),
DaysLeft < 0 DaysLeft < 0 andalso
andalso (Type =/= ?OFFICIAL) orelse small_customer_over_expired(CType, DaysLeft). (Type =/= ?OFFICIAL) orelse small_customer_over_expired(CType, DaysLeft).
small_customer_over_expired(?SMALL_CUSTOMER, DaysLeft) small_customer_over_expired(?SMALL_CUSTOMER, DaysLeft) when
when DaysLeft < ?EXPIRED_DAY -> true; DaysLeft < ?EXPIRED_DAY
small_customer_over_expired(_CType, _DaysLeft) -> false. ->
true;
small_customer_over_expired(_CType, _DaysLeft) ->
false.
apply_limits(Limits) -> apply_limits(Limits) ->
ets:insert(?LICENSE_TAB, {limits, Limits}). ets:insert(?LICENSE_TAB, {limits, Limits}).

View File

@ -26,36 +26,41 @@ license(["reload"]) ->
#{key := _Key} -> #{key := _Key} ->
?PRINT_MSG("License is not configured as a file, please specify file explicitly~n") ?PRINT_MSG("License is not configured as a file, please specify file explicitly~n")
end; end;
license(["reload", Filename]) -> license(["reload", Filename]) ->
case emqx_license:update_file(Filename) of case emqx_license:update_file(Filename) of
{ok, Warnings} -> {ok, Warnings} ->
ok = print_warnings(Warnings), ok = print_warnings(Warnings),
ok = ?PRINT_MSG("ok~n"); ok = ?PRINT_MSG("ok~n");
{error, Reason} -> ?PRINT("Error: ~p~n", [Reason]) {error, Reason} ->
?PRINT("Error: ~p~n", [Reason])
end; end;
license(["update", EncodedLicense]) -> license(["update", EncodedLicense]) ->
case emqx_license:update_key(EncodedLicense) of case emqx_license:update_key(EncodedLicense) of
{ok, Warnings} -> {ok, Warnings} ->
ok = print_warnings(Warnings), ok = print_warnings(Warnings),
ok = ?PRINT_MSG("ok~n"); ok = ?PRINT_MSG("ok~n");
{error, Reason} -> ?PRINT("Error: ~p~n", [Reason]) {error, Reason} ->
?PRINT("Error: ~p~n", [Reason])
end; end;
license(["info"]) -> license(["info"]) ->
lists:foreach(fun({K, V}) when is_binary(V); is_atom(V); is_list(V) -> lists:foreach(
fun
({K, V}) when is_binary(V); is_atom(V); is_list(V) ->
?PRINT("~-16s: ~s~n", [K, V]); ?PRINT("~-16s: ~s~n", [K, V]);
({K, V}) -> ({K, V}) ->
?PRINT("~-16s: ~p~n", [K, V]) ?PRINT("~-16s: ~p~n", [K, V])
end, emqx_license_checker:dump()); end,
emqx_license_checker:dump()
);
license(_) -> license(_) ->
emqx_ctl:usage( emqx_ctl:usage(
[ {"license info", "Show license info"}, [
{"license reload [<File>]", "Reload license from a file specified with an absolute path"}, {"license info", "Show license info"},
{"license reload [<File>]",
"Reload license from a file specified with an absolute path"},
{"license update License", "Update license given as a string"} {"license update License", "Update license given as a string"}
]). ]
).
unload() -> unload() ->
ok = emqx_ctl:unregister_command(license). ok = emqx_ctl:unregister_command(license).
@ -70,8 +75,10 @@ print_warnings(Warnings) ->
print_evaluation_warning(#{warn_evaluation := true}) -> print_evaluation_warning(#{warn_evaluation := true}) ->
?PRINT_MSG(?EVALUATION_LOG); ?PRINT_MSG(?EVALUATION_LOG);
print_evaluation_warning(_) -> ok. print_evaluation_warning(_) ->
ok.
print_expiry_warning(#{warn_expiry := true}) -> print_expiry_warning(#{warn_expiry := true}) ->
?PRINT_MSG(?EXPIRY_LOG); ?PRINT_MSG(?EXPIRY_LOG);
print_expiry_warning(_) -> ok. print_expiry_warning(_) ->
ok.

View File

@ -7,14 +7,18 @@
-behaviour(gen_server). -behaviour(gen_server).
-export([start_link/1, -export([
start_link/4]). start_link/1,
start_link/4
]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, -export([
init/1,
handle_call/3, handle_call/3,
handle_cast/2, handle_cast/2,
handle_info/2]). handle_info/2
]).
-define(NAME, emqx). -define(NAME, emqx).
-define(INTERVAL, 5000). -define(INTERVAL, 5000).
@ -35,7 +39,8 @@ start_link(Name, ServerName, Interval, Callback) ->
init([Name, Interval, Callback]) -> init([Name, Interval, Callback]) ->
Pid = whereis(Name), Pid = whereis(Name),
State = #{interval => Interval, State = #{
interval => Interval,
name => Name, name => Name,
pid => Pid, pid => Pid,
callback => Callback callback => Callback
@ -51,7 +56,6 @@ handle_cast(_Msg, State) ->
handle_info({timeout, Timer, check_pid}, #{timer := Timer} = State) -> handle_info({timeout, Timer, check_pid}, #{timer := Timer} = State) ->
NewState = check_pid(State), NewState = check_pid(State),
{noreply, ensure_timer(NewState)}; {noreply, ensure_timer(NewState)};
handle_info(_Msg, State) -> handle_info(_Msg, State) ->
{noreply, State}. {noreply, State}.
@ -60,7 +64,8 @@ handle_info(_Msg, State) ->
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
ensure_timer(#{interval := Interval} = State) -> ensure_timer(#{interval := Interval} = State) ->
_ = case State of _ =
case State of
#{timer := Timer} -> erlang:cancel_timer(Timer); #{timer := Timer} -> erlang:cancel_timer(Timer);
_ -> ok _ -> ok
end, end,

View File

@ -9,33 +9,38 @@
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include("emqx_license.hrl"). -include("emqx_license.hrl").
-define(PUBKEY, <<""" -define(PUBKEY, <<
-----BEGIN PUBLIC KEY----- ""
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbtkdos3TZmSv+D7+X5pc0yfcjum2 "\n"
Q1DK6PCWkiQihjvjJjKFzdYzcWOgC6f4Ou3mgGAUSjdQYYnFKZ/9f5ax4g== "-----BEGIN PUBLIC KEY-----\n"
-----END PUBLIC KEY----- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbtkdos3TZmSv+D7+X5pc0yfcjum2\n"
""">>). "Q1DK6PCWkiQihjvjJjKFzdYzcWOgC6f4Ou3mgGAUSjdQYYnFKZ/9f5ax4g==\n"
"-----END PUBLIC KEY-----\n"
""
>>).
-define(LICENSE_PARSE_MODULES, [emqx_license_parser_v20220101 -define(LICENSE_PARSE_MODULES, [emqx_license_parser_v20220101]).
]).
-type license_data() :: term(). -type license_data() :: term().
-type customer_type() :: ?SMALL_CUSTOMER | -type customer_type() ::
?MEDIUM_CUSTOMER | ?SMALL_CUSTOMER
?LARGE_CUSTOMER | | ?MEDIUM_CUSTOMER
?EVALUATION_CUSTOMER. | ?LARGE_CUSTOMER
| ?EVALUATION_CUSTOMER.
-type license_type() :: ?OFFICIAL | ?TRIAL. -type license_type() :: ?OFFICIAL | ?TRIAL.
-type license() :: #{module := module(), data := license_data()}. -type license() :: #{module := module(), data := license_data()}.
-export_type([license_data/0, -export_type([
license_data/0,
customer_type/0, customer_type/0,
license_type/0, license_type/0,
license/0]). license/0
]).
-export([
-export([parse/1, parse/1,
parse/2, parse/2,
dump/1, dump/1,
customer_type/1, customer_type/1,
@ -99,7 +104,6 @@ max_connections(#{module := Module, data := LicenseData}) ->
do_parse(_Content, _Key, [], Errors) -> do_parse(_Content, _Key, [], Errors) ->
{error, lists:reverse(Errors)}; {error, lists:reverse(Errors)};
do_parse(Content, Key, [Module | Modules], Errors) -> do_parse(Content, Key, [Module | Modules], Errors) ->
try Module:parse(Content, Key) of try Module:parse(Content, Key) of
{ok, LicenseData} -> {ok, LicenseData} ->

View File

@ -18,12 +18,14 @@
%% allow it to start from Nov.2021 %% allow it to start from Nov.2021
-define(MIN_START_DATE, 20211101). -define(MIN_START_DATE, 20211101).
-export([parse/2, -export([
parse/2,
dump/1, dump/1,
customer_type/1, customer_type/1,
license_type/1, license_type/1,
expiry_date/1, expiry_date/1,
max_connections/1]). max_connections/1
]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% API %% API
@ -40,25 +42,30 @@ parse(Content, Key) ->
{error, Reason} {error, Reason}
end. end.
dump(#{type := Type, dump(
#{
type := Type,
customer_type := CType, customer_type := CType,
customer := Customer, customer := Customer,
email := Email, email := Email,
date_start := DateStart, date_start := DateStart,
max_connections := MaxConns} = License) -> max_connections := MaxConns
} = License
) ->
DateExpiry = expiry_date(License), DateExpiry = expiry_date(License),
{DateNow, _} = calendar:universal_time(), {DateNow, _} = calendar:universal_time(),
Expiry = DateNow > DateExpiry, Expiry = DateNow > DateExpiry,
[{customer, Customer}, [
{customer, Customer},
{email, Email}, {email, Email},
{max_connections, MaxConns}, {max_connections, MaxConns},
{start_at, format_date(DateStart)}, {start_at, format_date(DateStart)},
{expiry_at, format_date(DateExpiry)}, {expiry_at, format_date(DateExpiry)},
{type, format_type(Type)}, {type, format_type(Type)},
{customer_type, CType}, {customer_type, CType},
{expiry, Expiry}]. {expiry, Expiry}
].
customer_type(#{customer_type := CType}) -> CType. customer_type(#{customer_type := CType}) -> CType.
@ -66,7 +73,8 @@ license_type(#{type := Type}) -> Type.
expiry_date(#{date_start := DateStart, days := Days}) -> expiry_date(#{date_start := DateStart, days := Days}) ->
calendar:gregorian_days_to_date( calendar:gregorian_days_to_date(
calendar:date_to_gregorian_days(DateStart) + Days). calendar:date_to_gregorian_days(DateStart) + Days
).
max_connections(#{max_connections := MaxConns}) -> max_connections(#{max_connections := MaxConns}) ->
MaxConns. MaxConns.
@ -92,16 +100,19 @@ verify_signature(Payload, Signature, Key) ->
parse_payload(Payload) -> parse_payload(Payload) ->
Lines = lists:map( Lines = lists:map(
fun string:trim/1, fun string:trim/1,
string:split(string:trim(Payload), <<"\n">>, all)), string:split(string:trim(Payload), <<"\n">>, all)
),
case Lines of case Lines of
[?LICENSE_VERSION, Type, CType, Customer, Email, DateStart, Days, MaxConns] -> [?LICENSE_VERSION, Type, CType, Customer, Email, DateStart, Days, MaxConns] ->
collect_fields([{type, parse_type(Type)}, collect_fields([
{type, parse_type(Type)},
{customer_type, parse_customer_type(CType)}, {customer_type, parse_customer_type(CType)},
{customer, {ok, Customer}}, {customer, {ok, Customer}},
{email, {ok, Email}}, {email, {ok, Email}},
{date_start, parse_date_start(DateStart)}, {date_start, parse_date_start(DateStart)},
{days, parse_days(Days)}, {days, parse_days(Days)},
{max_connections, parse_max_connections(MaxConns)}]); {max_connections, parse_max_connections(MaxConns)}
]);
[_Version, _Type, _CType, _Customer, _Email, _DateStart, _Days, _MaxConns] -> [_Version, _Type, _CType, _Customer, _Email, _DateStart, _Days, _MaxConns] ->
{error, invalid_version}; {error, invalid_version};
_ -> _ ->
@ -155,13 +166,15 @@ parse_int(Str0) ->
collect_fields(Fields) -> collect_fields(Fields) ->
Collected = lists:foldl( Collected = lists:foldl(
fun({Name, {ok, Value}}, {FieldValues, Errors}) -> fun
({Name, {ok, Value}}, {FieldValues, Errors}) ->
{[{Name, Value} | FieldValues], Errors}; {[{Name, Value} | FieldValues], Errors};
({Name, {error, Reason}}, {FieldValues, Errors}) -> ({Name, {error, Reason}}, {FieldValues, Errors}) ->
{FieldValues, [{Name, Reason} | Errors]} {FieldValues, [{Name, Reason} | Errors]}
end, end,
{[], []}, {[], []},
Fields), Fields
),
case Collected of case Collected of
{FieldValues, []} -> {FieldValues, []} ->
{ok, maps:from_list(FieldValues)}; {ok, maps:from_list(FieldValues)};
@ -173,7 +186,9 @@ format_date({Year, Month, Day}) ->
iolist_to_binary( iolist_to_binary(
io_lib:format( io_lib:format(
"~4..0w-~2..0w-~2..0w", "~4..0w-~2..0w-~2..0w",
[Year, Month, Day])). [Year, Month, Day]
)
).
format_type(?OFFICIAL) -> <<"official">>; format_type(?OFFICIAL) -> <<"official">>;
format_type(?TRIAL) -> <<"trial">>. format_type(?TRIAL) -> <<"trial">>.

View File

@ -10,18 +10,22 @@
-define(CHECK_INTERVAL, 5000). -define(CHECK_INTERVAL, 5000).
-export([start_link/0, -export([
start_link/0,
start_link/1, start_link/1,
local_connection_count/0, local_connection_count/0,
connection_count/0]). connection_count/0
]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, -export([
init/1,
handle_call/3, handle_call/3,
handle_cast/2, handle_cast/2,
handle_info/2, handle_info/2,
terminate/2, terminate/2,
code_change/3]). code_change/3
]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% API %% API
@ -83,14 +87,17 @@ connection_quota_early_alarm({ok, #{max_connections := Max}}) when is_integer(Ma
if if
Count > Max * High -> Count > Max * High ->
HighPercent = float_to_binary(High * 100, [{decimals, 0}]), HighPercent = float_to_binary(High * 100, [{decimals, 0}]),
Message = iolist_to_binary(["License: live connection number exceeds ", HighPercent, "%"]), Message = iolist_to_binary([
"License: live connection number exceeds ", HighPercent, "%"
]),
catch emqx_alarm:activate(license_quota, #{high_watermark => HighPercent}, Message); catch emqx_alarm:activate(license_quota, #{high_watermark => HighPercent}, Message);
Count < Max * Low -> Count < Max * Low ->
catch emqx_alarm:deactivate(license_quota); catch emqx_alarm:deactivate(license_quota);
true -> true ->
ok ok
end; end;
connection_quota_early_alarm(_Limits) -> ok. connection_quota_early_alarm(_Limits) ->
ok.
cached_remote_connection_count() -> cached_remote_connection_count() ->
try ets:lookup(?MODULE, remote_connection_count) of try ets:lookup(?MODULE, remote_connection_count) of
@ -104,7 +111,8 @@ update_resources() ->
ets:insert(?MODULE, {remote_connection_count, remote_connection_count()}). ets:insert(?MODULE, {remote_connection_count, remote_connection_count()}).
ensure_timer(#{check_peer_interval := CheckInterval} = State) -> ensure_timer(#{check_peer_interval := CheckInterval} = State) ->
_ = case State of _ =
case State of
#{timer := Timer} -> erlang:cancel_timer(Timer); #{timer := Timer} -> erlang:cancel_timer(Timer);
_ -> ok _ -> ok
end, end,

View File

@ -14,37 +14,56 @@
-export([roots/0, fields/1, validations/0]). -export([roots/0, fields/1, validations/0]).
roots() -> [{license, roots() ->
hoconsc:mk(hoconsc:union([hoconsc:ref(?MODULE, key_license), [
hoconsc:ref(?MODULE, file_license)]), {license,
#{desc => """EMQX Enterprise license. hoconsc:mk(
A license is either a `key` or a `file`. hoconsc:union([
When `key` and `file` are both configured, `key` is used. hoconsc:ref(?MODULE, key_license),
hoconsc:ref(?MODULE, file_license)
EMQX by default starts with a trial license. For a different license, ]),
visit https://www.emqx.com/apply-licenses/emqx to apply. #{
"})} desc =>
"EMQX Enterprise license.\n"
"A license is either a `key` or a `file`.\n"
"When `key` and `file` are both configured, `key` is used.\n"
"\n"
"EMQX by default starts with a trial license. For a different license,\n"
"visit https://www.emqx.com/apply-licenses/emqx to apply.\n"
}
)}
]. ].
fields(key_license) -> fields(key_license) ->
[ {key, #{type => string(), [
sensitive => true, %% so it's not logged {key, #{
type => string(),
%% so it's not logged
sensitive => true,
desc => "Configure the license as a string" desc => "Configure the license as a string"
}} }}
| common_fields()]; | common_fields()
];
fields(file_license) -> fields(file_license) ->
[ {file, #{type => string(), [
{file, #{
type => string(),
desc => "Path to the license file" desc => "Path to the license file"
}} }}
| common_fields()]. | common_fields()
].
common_fields() -> common_fields() ->
[ [
{connection_low_watermark, #{type => emqx_schema:percent(), {connection_low_watermark, #{
default => "75%", desc => "" type => emqx_schema:percent(),
default => "75%",
desc => ""
}}, }},
{connection_high_watermark, #{type => emqx_schema:percent(), {connection_high_watermark, #{
default => "80%", desc => "" type => emqx_schema:percent(),
default => "80%",
desc => ""
}} }}
]. ].
@ -53,7 +72,8 @@ validations() ->
check_license_watermark(Conf) -> check_license_watermark(Conf) ->
case hocon_maps:get("license.connection_low_watermark", Conf) of case hocon_maps:get("license.connection_low_watermark", Conf) of
undefined -> true; undefined ->
true;
Low -> Low ->
High = hocon_maps:get("license.connection_high_watermark", Conf), High = hocon_maps:get("license.connection_high_watermark", Conf),
case High =/= undefined andalso High > Low of case High =/= undefined andalso High > Low of

View File

@ -16,28 +16,40 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) -> init([]) ->
{ok, {#{strategy => one_for_one, {ok,
{
#{
strategy => one_for_one,
intensity => 10, intensity => 10,
period => 100}, period => 100
},
[#{id => license_checker, [
#{
id => license_checker,
start => {emqx_license_checker, start_link, [fun emqx_license:read_license/0]}, start => {emqx_license_checker, start_link, [fun emqx_license:read_license/0]},
restart => permanent, restart => permanent,
shutdown => 5000, shutdown => 5000,
type => worker, type => worker,
modules => [emqx_license_checker]}, modules => [emqx_license_checker]
},
#{id => license_resources, #{
id => license_resources,
start => {emqx_license_resources, start_link, []}, start => {emqx_license_resources, start_link, []},
restart => permanent, restart => permanent,
shutdown => 5000, shutdown => 5000,
type => worker, type => worker,
modules => [emqx_license_resources]}, modules => [emqx_license_resources]
},
#{id => license_installer, #{
id => license_installer,
start => {emqx_license_installer, start_link, [fun emqx_license:load/0]}, start => {emqx_license_installer, start_link, [fun emqx_license:load/0]},
restart => permanent, restart => permanent,
shutdown => 5000, shutdown => 5000,
type => worker, type => worker,
modules => [emqx_license_installer]} modules => [emqx_license_installer]
]}}. }
]
}}.

View File

@ -51,8 +51,8 @@ set_special_configs(emqx_license) ->
emqx_config:put([license], Config), emqx_config:put([license], Config),
RawConfig = #{<<"file">> => emqx_license_test_lib:default_license()}, RawConfig = #{<<"file">> => emqx_license_test_lib:default_license()},
emqx_config:put_raw([<<"license">>], RawConfig); emqx_config:put_raw([<<"license">>], RawConfig);
set_special_configs(_) ->
set_special_configs(_) -> ok. ok.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Tests %% Tests
@ -61,43 +61,52 @@ set_special_configs(_) -> ok.
t_update_file(_Config) -> t_update_file(_Config) ->
?assertMatch( ?assertMatch(
{error, {invalid_license_file, enoent}}, {error, {invalid_license_file, enoent}},
emqx_license:update_file("/unknown/path")), emqx_license:update_file("/unknown/path")
),
ok = file:write_file("license_with_invalid_content.lic", <<"bad license">>), ok = file:write_file("license_with_invalid_content.lic", <<"bad license">>),
?assertMatch( ?assertMatch(
{error, [_ | _]}, {error, [_ | _]},
emqx_license:update_file("license_with_invalid_content.lic")), emqx_license:update_file("license_with_invalid_content.lic")
),
?assertMatch( ?assertMatch(
{ok, #{}}, {ok, #{}},
emqx_license:update_file(emqx_license_test_lib:default_license())). emqx_license:update_file(emqx_license_test_lib:default_license())
).
t_update_value(_Config) -> t_update_value(_Config) ->
?assertMatch( ?assertMatch(
{error, [_ | _]}, {error, [_ | _]},
emqx_license:update_key("invalid.license")), emqx_license:update_key("invalid.license")
),
{ok, LicenseValue} = file:read_file(emqx_license_test_lib:default_license()), {ok, LicenseValue} = file:read_file(emqx_license_test_lib:default_license()),
?assertMatch( ?assertMatch(
{ok, #{}}, {ok, #{}},
emqx_license:update_key(LicenseValue)). emqx_license:update_key(LicenseValue)
).
t_read_license_from_invalid_file(_Config) -> t_read_license_from_invalid_file(_Config) ->
?assertMatch( ?assertMatch(
{error, enoent}, {error, enoent},
emqx_license:read_license()). emqx_license:read_license()
).
t_check_exceeded(_Config) -> t_check_exceeded(_Config) ->
License = mk_license( License = mk_license(
["220111", [
"220111",
"0", "0",
"10", "10",
"Foo", "Foo",
"contact@foo.com", "contact@foo.com",
"20220111", "20220111",
"100000", "100000",
"10"]), "10"
]
),
#{} = emqx_license_checker:update(License), #{} = emqx_license_checker:update(License),
ok = lists:foreach( ok = lists:foreach(
@ -105,22 +114,27 @@ t_check_exceeded(_Config) ->
{ok, C} = emqtt:start_link(), {ok, C} = emqtt:start_link(),
{ok, _} = emqtt:connect(C) {ok, _} = emqtt:connect(C)
end, end,
lists:seq(1, 12)), lists:seq(1, 12)
),
?assertEqual( ?assertEqual(
{stop, {error, ?RC_QUOTA_EXCEEDED}}, {stop, {error, ?RC_QUOTA_EXCEEDED}},
emqx_license:check(#{}, #{})). emqx_license:check(#{}, #{})
).
t_check_ok(_Config) -> t_check_ok(_Config) ->
License = mk_license( License = mk_license(
["220111", [
"220111",
"0", "0",
"10", "10",
"Foo", "Foo",
"contact@foo.com", "contact@foo.com",
"20220111", "20220111",
"100000", "100000",
"10"]), "10"
]
),
#{} = emqx_license_checker:update(License), #{} = emqx_license_checker:update(License),
ok = lists:foreach( ok = lists:foreach(
@ -128,33 +142,43 @@ t_check_ok(_Config) ->
{ok, C} = emqtt:start_link(), {ok, C} = emqtt:start_link(),
{ok, _} = emqtt:connect(C) {ok, _} = emqtt:connect(C)
end, end,
lists:seq(1, 11)), lists:seq(1, 11)
),
?assertEqual( ?assertEqual(
{ok, #{}}, {ok, #{}},
emqx_license:check(#{}, #{})). emqx_license:check(#{}, #{})
).
t_check_expired(_Config) -> t_check_expired(_Config) ->
License = mk_license( License = mk_license(
["220111", [
"1", %% Official customer "220111",
"0", %% Small customer %% Official customer
"1",
%% Small customer
"0",
"Foo", "Foo",
"contact@foo.com", "contact@foo.com",
"20211101", %% Expired long ago %% Expired long ago
"20211101",
"10", "10",
"10"]), "10"
]
),
#{} = emqx_license_checker:update(License), #{} = emqx_license_checker:update(License),
?assertEqual( ?assertEqual(
{stop, {error, ?RC_QUOTA_EXCEEDED}}, {stop, {error, ?RC_QUOTA_EXCEEDED}},
emqx_license:check(#{}, #{})). emqx_license:check(#{}, #{})
).
t_check_not_loaded(_Config) -> t_check_not_loaded(_Config) ->
ok = emqx_license_checker:purge(), ok = emqx_license_checker:purge(),
?assertEqual( ?assertEqual(
{stop, {error, ?RC_QUOTA_EXCEEDED}}, {stop, {error, ?RC_QUOTA_EXCEEDED}},
emqx_license:check(#{}, #{})). emqx_license:check(#{}, #{})
).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
@ -164,5 +188,6 @@ mk_license(Fields) ->
EncodedLicense = emqx_license_test_lib:make_license(Fields), EncodedLicense = emqx_license_test_lib:make_license(Fields),
{ok, License} = emqx_license_parser:parse( {ok, License} = emqx_license_parser:parse(
EncodedLicense, EncodedLicense,
emqx_license_test_lib:public_key_pem()), emqx_license_test_lib:public_key_pem()
),
License. License.

View File

@ -25,22 +25,20 @@ end_per_suite(_) ->
init_per_testcase(t_default_limits, Config) -> init_per_testcase(t_default_limits, Config) ->
ok = emqx_common_test_helpers:stop_apps([emqx_license]), ok = emqx_common_test_helpers:stop_apps([emqx_license]),
Config; Config;
init_per_testcase(_Case, Config) -> init_per_testcase(_Case, Config) ->
{ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000), {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000),
Config. Config.
end_per_testcase(t_default_limits, _Config) -> end_per_testcase(t_default_limits, _Config) ->
ok = emqx_common_test_helpers:start_apps([emqx_license], fun set_special_configs/1); ok = emqx_common_test_helpers:start_apps([emqx_license], fun set_special_configs/1);
end_per_testcase(_Case, _Config) -> end_per_testcase(_Case, _Config) ->
ok. ok.
set_special_configs(emqx_license) -> set_special_configs(emqx_license) ->
Config = #{file => emqx_license_test_lib:default_license()}, Config = #{file => emqx_license_test_lib:default_license()},
emqx_config:put([license], Config); emqx_config:put([license], Config);
set_special_configs(_) ->
set_special_configs(_) -> ok. ok.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Tests %% Tests
@ -51,43 +49,53 @@ t_default_limits(_Config) ->
t_dump(_Config) -> t_dump(_Config) ->
License = mk_license( License = mk_license(
["220111", [
"220111",
"0", "0",
"10", "10",
"Foo", "Foo",
"contact@foo.com", "contact@foo.com",
"20220111", "20220111",
"100000", "100000",
"10"]), "10"
]
),
#{} = emqx_license_checker:update(License), #{} = emqx_license_checker:update(License),
?assertEqual( ?assertEqual(
[{customer,<<"Foo">>}, [
{customer, <<"Foo">>},
{email, <<"contact@foo.com">>}, {email, <<"contact@foo.com">>},
{max_connections, 10}, {max_connections, 10},
{start_at, <<"2022-01-11">>}, {start_at, <<"2022-01-11">>},
{expiry_at, <<"2295-10-27">>}, {expiry_at, <<"2295-10-27">>},
{type, <<"trial">>}, {type, <<"trial">>},
{customer_type, 10}, {customer_type, 10},
{expiry,false}], {expiry, false}
emqx_license_checker:dump()). ],
emqx_license_checker:dump()
).
t_update(_Config) -> t_update(_Config) ->
License = mk_license( License = mk_license(
["220111", [
"220111",
"0", "0",
"10", "10",
"Foo", "Foo",
"contact@foo.com", "contact@foo.com",
"20220111", "20220111",
"100000", "100000",
"123"]), "123"
]
),
#{} = emqx_license_checker:update(License), #{} = emqx_license_checker:update(License),
?assertMatch( ?assertMatch(
{ok, #{max_connections := 123}}, {ok, #{max_connections := 123}},
emqx_license_checker:limits()). emqx_license_checker:limits()
).
t_update_by_timer(_Config) -> t_update_by_timer(_Config) ->
?check_trace( ?check_trace(
@ -96,94 +104,117 @@ t_update_by_timer(_Config) ->
begin begin
erlang:send( erlang:send(
emqx_license_checker, emqx_license_checker,
check_license) check_license
)
end, end,
#{?snk_kind := emqx_license_checked}, #{?snk_kind := emqx_license_checked},
1000) 1000
)
end, end,
fun(Trace) -> fun(Trace) ->
?assertMatch([_ | _], ?of_kind(emqx_license_checked, Trace)) ?assertMatch([_ | _], ?of_kind(emqx_license_checked, Trace))
end). end
).
t_expired_trial(_Config) -> t_expired_trial(_Config) ->
{NowDate, _} = calendar:universal_time(), {NowDate, _} = calendar:universal_time(),
Date10DaysAgo = calendar:gregorian_days_to_date( Date10DaysAgo = calendar:gregorian_days_to_date(
calendar:date_to_gregorian_days(NowDate) - 10), calendar:date_to_gregorian_days(NowDate) - 10
),
License = mk_license( License = mk_license(
["220111", [
"220111",
"0", "0",
"10", "10",
"Foo", "Foo",
"contact@foo.com", "contact@foo.com",
format_date(Date10DaysAgo), format_date(Date10DaysAgo),
"1", "1",
"123"]), "123"
]
),
#{} = emqx_license_checker:update(License), #{} = emqx_license_checker:update(License),
?assertMatch( ?assertMatch(
{ok, #{max_connections := expired}}, {ok, #{max_connections := expired}},
emqx_license_checker:limits()). emqx_license_checker:limits()
).
t_overexpired_small_client(_Config) -> t_overexpired_small_client(_Config) ->
{NowDate, _} = calendar:universal_time(), {NowDate, _} = calendar:universal_time(),
Date100DaysAgo = calendar:gregorian_days_to_date( Date100DaysAgo = calendar:gregorian_days_to_date(
calendar:date_to_gregorian_days(NowDate) - 100), calendar:date_to_gregorian_days(NowDate) - 100
),
License = mk_license( License = mk_license(
["220111", [
"220111",
"1", "1",
"0", "0",
"Foo", "Foo",
"contact@foo.com", "contact@foo.com",
format_date(Date100DaysAgo), format_date(Date100DaysAgo),
"1", "1",
"123"]), "123"
]
),
#{} = emqx_license_checker:update(License), #{} = emqx_license_checker:update(License),
?assertMatch( ?assertMatch(
{ok, #{max_connections := expired}}, {ok, #{max_connections := expired}},
emqx_license_checker:limits()). emqx_license_checker:limits()
).
t_overexpired_medium_client(_Config) -> t_overexpired_medium_client(_Config) ->
{NowDate, _} = calendar:universal_time(), {NowDate, _} = calendar:universal_time(),
Date100DaysAgo = calendar:gregorian_days_to_date( Date100DaysAgo = calendar:gregorian_days_to_date(
calendar:date_to_gregorian_days(NowDate) - 100), calendar:date_to_gregorian_days(NowDate) - 100
),
License = mk_license( License = mk_license(
["220111", [
"220111",
"1", "1",
"1", "1",
"Foo", "Foo",
"contact@foo.com", "contact@foo.com",
format_date(Date100DaysAgo), format_date(Date100DaysAgo),
"1", "1",
"123"]), "123"
]
),
#{} = emqx_license_checker:update(License), #{} = emqx_license_checker:update(License),
?assertMatch( ?assertMatch(
{ok, #{max_connections := 123}}, {ok, #{max_connections := 123}},
emqx_license_checker:limits()). emqx_license_checker:limits()
).
t_recently_expired_small_client(_Config) -> t_recently_expired_small_client(_Config) ->
{NowDate, _} = calendar:universal_time(), {NowDate, _} = calendar:universal_time(),
Date10DaysAgo = calendar:gregorian_days_to_date( Date10DaysAgo = calendar:gregorian_days_to_date(
calendar:date_to_gregorian_days(NowDate) - 10), calendar:date_to_gregorian_days(NowDate) - 10
),
License = mk_license( License = mk_license(
["220111", [
"220111",
"1", "1",
"0", "0",
"Foo", "Foo",
"contact@foo.com", "contact@foo.com",
format_date(Date10DaysAgo), format_date(Date10DaysAgo),
"1", "1",
"123"]), "123"
]
),
#{} = emqx_license_checker:update(License), #{} = emqx_license_checker:update(License),
?assertMatch( ?assertMatch(
{ok, #{max_connections := 123}}, {ok, #{max_connections := 123}},
emqx_license_checker:limits()). emqx_license_checker:limits()
).
t_unknown_calls(_Config) -> t_unknown_calls(_Config) ->
ok = gen_server:cast(emqx_license_checker, some_cast), ok = gen_server:cast(emqx_license_checker, some_cast),
@ -198,11 +229,14 @@ mk_license(Fields) ->
EncodedLicense = emqx_license_test_lib:make_license(Fields), EncodedLicense = emqx_license_test_lib:make_license(Fields),
{ok, License} = emqx_license_parser:parse( {ok, License} = emqx_license_parser:parse(
EncodedLicense, EncodedLicense,
emqx_license_test_lib:public_key_pem()), emqx_license_test_lib:public_key_pem()
),
License. License.
format_date({Year, Month, Day}) -> format_date({Year, Month, Day}) ->
lists:flatten( lists:flatten(
io_lib:format( io_lib:format(
"~4..0w~2..0w~2..0w", "~4..0w~2..0w~2..0w",
[Year, Month, Day])). [Year, Month, Day]
)
).

View File

@ -35,8 +35,8 @@ set_special_configs(emqx_license) ->
emqx_config:put([license], Config), emqx_config:put([license], Config),
RawConfig = #{<<"file">> => emqx_license_test_lib:default_license()}, RawConfig = #{<<"file">> => emqx_license_test_lib:default_license()},
emqx_config:put_raw([<<"license">>], RawConfig); emqx_config:put_raw([<<"license">>], RawConfig);
set_special_configs(_) ->
set_special_configs(_) -> ok. ok.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Tests %% Tests
@ -58,4 +58,3 @@ t_update(_Config) ->
_ = emqx_license_cli:license(["update", LicenseValue]), _ = emqx_license_cli:license(["update", LicenseValue]),
_ = emqx_license_cli:license(["reload"]), _ = emqx_license_cli:license(["reload"]),
_ = emqx_license_cli:license(["update", "Invalid License Value"]). _ = emqx_license_cli:license(["update", "Invalid License Value"]).

View File

@ -33,8 +33,8 @@ end_per_testcase(_Case, _Config) ->
set_special_configs(emqx_license) -> set_special_configs(emqx_license) ->
Config = #{file => emqx_license_test_lib:default_license()}, Config = #{file => emqx_license_test_lib:default_license()},
emqx_config:put([license], Config); emqx_config:put([license], Config);
set_special_configs(_) ->
set_special_configs(_) -> ok. ok.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Tests %% Tests
@ -45,35 +45,43 @@ t_update(_Config) ->
begin begin
?wait_async_action( ?wait_async_action(
begin begin
Pid0 = spawn_link(fun() -> receive exit -> ok end end), Pid0 = spawn_link(fun() ->
receive
exit -> ok
end
end),
register(installer_test, Pid0), register(installer_test, Pid0),
{ok, _} = emqx_license_installer:start_link( {ok, _} = emqx_license_installer:start_link(
installer_test, installer_test,
?MODULE, ?MODULE,
10, 10,
fun() -> ok end), fun() -> ok end
),
{ok, _} = ?block_until( {ok, _} = ?block_until(
#{?snk_kind := emqx_license_installer_nochange}, #{?snk_kind := emqx_license_installer_nochange},
100), 100
),
Pid0 ! exit, Pid0 ! exit,
{ok, _} = ?block_until( {ok, _} = ?block_until(
#{?snk_kind := emqx_license_installer_noproc}, #{?snk_kind := emqx_license_installer_noproc},
100), 100
),
Pid1 = spawn_link(fun() -> timer:sleep(100) end), Pid1 = spawn_link(fun() -> timer:sleep(100) end),
register(installer_test, Pid1) register(installer_test, Pid1)
end, end,
#{?snk_kind := emqx_license_installer_called}, #{?snk_kind := emqx_license_installer_called},
1000) 1000
)
end, end,
fun(Trace) -> fun(Trace) ->
?assertMatch([_ | _], ?of_kind(emqx_license_installer_called, Trace)) ?assertMatch([_ | _], ?of_kind(emqx_license_installer_called, Trace))
end). end
).
t_unknown_calls(_Config) -> t_unknown_calls(_Config) ->
ok = gen_server:cast(emqx_license_installer, some_cast), ok = gen_server:cast(emqx_license_installer, some_cast),

View File

@ -32,8 +32,8 @@ end_per_testcase(_Case, _Config) ->
set_special_configs(emqx_license) -> set_special_configs(emqx_license) ->
Config = #{file => emqx_license_test_lib:default_license()}, Config = #{file => emqx_license_test_lib:default_license()},
emqx_config:put([license], Config); emqx_config:put([license], Config);
set_special_configs(_) ->
set_special_configs(_) -> ok. ok.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Tests %% Tests
@ -44,11 +44,11 @@ t_parse(_Config) ->
%% invalid version %% invalid version
?assertMatch( ?assertMatch(
{error, {error, [{emqx_license_parser_v20220101, invalid_version}]},
[{emqx_license_parser_v20220101,invalid_version}]},
emqx_license_parser:parse( emqx_license_parser:parse(
emqx_license_test_lib:make_license( emqx_license_test_lib:make_license(
["220101", [
"220101",
"0", "0",
"10", "10",
"Foo", "Foo",
@ -56,36 +56,46 @@ t_parse(_Config) ->
"20220111", "20220111",
"100000", "100000",
"10" "10"
]), ]
public_key_pem())), ),
public_key_pem()
)
),
%% invalid field number %% invalid field number
?assertMatch( ?assertMatch(
{error, {error, [{emqx_license_parser_v20220101, invalid_field_number}]},
[{emqx_license_parser_v20220101,invalid_field_number}]},
emqx_license_parser:parse( emqx_license_parser:parse(
emqx_license_test_lib:make_license( emqx_license_test_lib:make_license(
["220111", [
"220111",
"0", "0",
"10", "10",
"Foo", "Bar", "Foo",
"Bar",
"contact@foo.com", "contact@foo.com",
"20220111", "20220111",
"100000", "100000",
"10" "10"
]), ]
public_key_pem())), ),
public_key_pem()
)
),
?assertMatch( ?assertMatch(
{error, {error, [
[{emqx_license_parser_v20220101, {emqx_license_parser_v20220101, [
[{type,invalid_license_type}, {type, invalid_license_type},
{customer_type, invalid_customer_type}, {customer_type, invalid_customer_type},
{date_start, invalid_date}, {date_start, invalid_date},
{days,invalid_int_value}]}]}, {days, invalid_int_value}
]}
]},
emqx_license_parser:parse( emqx_license_parser:parse(
emqx_license_test_lib:make_license( emqx_license_test_lib:make_license(
["220111", [
"220111",
"zero", "zero",
"ten", "ten",
"Foo", "Foo",
@ -93,19 +103,25 @@ t_parse(_Config) ->
"20220231", "20220231",
"-10", "-10",
"10" "10"
]), ]
public_key_pem())), ),
public_key_pem()
)
),
?assertMatch( ?assertMatch(
{error, {error, [
[{emqx_license_parser_v20220101, {emqx_license_parser_v20220101, [
[{type,invalid_license_type}, {type, invalid_license_type},
{customer_type, invalid_customer_type}, {customer_type, invalid_customer_type},
{date_start, invalid_date}, {date_start, invalid_date},
{days,invalid_int_value}]}]}, {days, invalid_int_value}
]}
]},
emqx_license_parser:parse( emqx_license_parser:parse(
emqx_license_test_lib:make_license( emqx_license_test_lib:make_license(
["220111", [
"220111",
"zero", "zero",
"ten", "ten",
"Foo", "Foo",
@ -113,66 +129,85 @@ t_parse(_Config) ->
"2022-02-1st", "2022-02-1st",
"-10", "-10",
"10" "10"
]), ]
public_key_pem())), ),
public_key_pem()
)
),
%% invalid signature %% invalid signature
[LicensePart, _] = binary:split( [LicensePart, _] = binary:split(
emqx_license_test_lib:make_license( emqx_license_test_lib:make_license(
["220111", [
"220111",
"0", "0",
"10", "10",
"Foo", "Foo",
"contact@foo.com", "contact@foo.com",
"20220111", "20220111",
"100000", "100000",
"10"]), "10"
<<".">>), ]
),
<<".">>
),
[_, SignaturePart] = binary:split( [_, SignaturePart] = binary:split(
emqx_license_test_lib:make_license( emqx_license_test_lib:make_license(
["220111", [
"220111",
"1", "1",
"10", "10",
"Foo", "Foo",
"contact@foo.com", "contact@foo.com",
"20220111", "20220111",
"100000", "100000",
"10"]), "10"
<<".">>), ]
),
<<".">>
),
?assertMatch( ?assertMatch(
{error, {error, [{emqx_license_parser_v20220101, invalid_signature}]},
[{emqx_license_parser_v20220101,invalid_signature}]},
emqx_license_parser:parse( emqx_license_parser:parse(
iolist_to_binary([LicensePart, <<".">>, SignaturePart]), iolist_to_binary([LicensePart, <<".">>, SignaturePart]),
public_key_pem())), public_key_pem()
)
),
%% totally invalid strings as license %% totally invalid strings as license
?assertMatch( ?assertMatch(
{error, [_ | _]}, {error, [_ | _]},
emqx_license_parser:parse( emqx_license_parser:parse(
<<"badlicense">>, <<"badlicense">>,
public_key_pem())), public_key_pem()
)
),
?assertMatch( ?assertMatch(
{error, [_ | _]}, {error, [_ | _]},
emqx_license_parser:parse( emqx_license_parser:parse(
<<"bad.license">>, <<"bad.license">>,
public_key_pem())). public_key_pem()
)
).
t_dump(_Config) -> t_dump(_Config) ->
{ok, License} = emqx_license_parser:parse(sample_license(), public_key_pem()), {ok, License} = emqx_license_parser:parse(sample_license(), public_key_pem()),
?assertEqual( ?assertEqual(
[{customer,<<"Foo">>}, [
{customer, <<"Foo">>},
{email, <<"contact@foo.com">>}, {email, <<"contact@foo.com">>},
{max_connections, 10}, {max_connections, 10},
{start_at, <<"2022-01-11">>}, {start_at, <<"2022-01-11">>},
{expiry_at, <<"2295-10-27">>}, {expiry_at, <<"2295-10-27">>},
{type, <<"trial">>}, {type, <<"trial">>},
{customer_type, 10}, {customer_type, 10},
{expiry,false}], {expiry, false}
emqx_license_parser:dump(License)). ],
emqx_license_parser:dump(License)
).
t_customer_type(_Config) -> t_customer_type(_Config) ->
{ok, License} = emqx_license_parser:parse(sample_license(), public_key_pem()), {ok, License} = emqx_license_parser:parse(sample_license(), public_key_pem()),
@ -203,11 +238,14 @@ public_key_pem() ->
sample_license() -> sample_license() ->
emqx_license_test_lib:make_license( emqx_license_test_lib:make_license(
["220111", [
"220111",
"0", "0",
"10", "10",
"Foo", "Foo",
"contact@foo.com", "contact@foo.com",
"20220111", "20220111",
"100,000", "100,000",
"10"]). "10"
]
).

View File

@ -33,8 +33,8 @@ end_per_testcase(_Case, _Config) ->
set_special_configs(emqx_license) -> set_special_configs(emqx_license) ->
Config = #{file => emqx_license_test_lib:default_license()}, Config = #{file => emqx_license_test_lib:default_license()},
emqx_config:put([license], Config); emqx_config:put([license], Config);
set_special_configs(_) ->
set_special_configs(_) -> ok. ok.
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Tests %% Tests
@ -46,14 +46,15 @@ t_connection_count(_Config) ->
?wait_async_action( ?wait_async_action(
whereis(emqx_license_resources) ! update_resources, whereis(emqx_license_resources) ! update_resources,
#{?snk_kind := emqx_license_resources_updated}, #{?snk_kind := emqx_license_resources_updated},
1000), 1000
),
emqx_license_resources:connection_count() emqx_license_resources:connection_count()
end, end,
fun(ConnCount, Trace) -> fun(ConnCount, Trace) ->
?assertEqual(0, ConnCount), ?assertEqual(0, ConnCount),
?assertMatch([_ | _], ?of_kind(emqx_license_resources_updated, Trace)) ?assertMatch([_ | _], ?of_kind(emqx_license_resources_updated, Trace))
end), end
),
meck:new(emqx_cm, [passthrough]), meck:new(emqx_cm, [passthrough]),
meck:expect(emqx_cm, get_connected_client_count, fun() -> 10 end), meck:expect(emqx_cm, get_connected_client_count, fun() -> 10 end),
@ -64,19 +65,22 @@ t_connection_count(_Config) ->
remote_connection_counts, remote_connection_counts,
fun(_Nodes) -> fun(_Nodes) ->
[{ok, 5}, {error, some_error}] [{ok, 5}, {error, some_error}]
end), end
),
?check_trace( ?check_trace(
begin begin
?wait_async_action( ?wait_async_action(
whereis(emqx_license_resources) ! update_resources, whereis(emqx_license_resources) ! update_resources,
#{?snk_kind := emqx_license_resources_updated}, #{?snk_kind := emqx_license_resources_updated},
1000), 1000
),
emqx_license_resources:connection_count() emqx_license_resources:connection_count()
end, end,
fun(ConnCount, _Trace) -> fun(ConnCount, _Trace) ->
?assertEqual(15, ConnCount) ?assertEqual(15, ConnCount)
end), end
),
meck:unload(emqx_license_proto_v1), meck:unload(emqx_license_proto_v1),
meck:unload(emqx_cm). meck:unload(emqx_cm).

View File

@ -7,15 +7,16 @@
-compile(nowarn_export_all). -compile(nowarn_export_all).
-compile(export_all). -compile(export_all).
-define(DEFAULT_LICENSE_VALUES, -define(DEFAULT_LICENSE_VALUES, [
["220111", "220111",
"0", "0",
"10", "10",
"Foo", "Foo",
"contact@foo.com", "contact@foo.com",
"20220111", "20220111",
"100000", "100000",
"10"]). "10"
]).
-define(DEFAULT_LICENSE_FILE, "emqx.lic"). -define(DEFAULT_LICENSE_FILE, "emqx.lic").
@ -36,7 +37,8 @@ test_key(Filename, Format) ->
Path = filename:join([Dir, "data", Filename]), Path = filename:join([Dir, "data", Filename]),
{ok, KeyData} = file:read_file(Path), {ok, KeyData} = file:read_file(Path),
case Format of case Format of
pem -> KeyData; pem ->
KeyData;
decoded -> decoded ->
[PemEntry] = public_key:pem_decode(KeyData), [PemEntry] = public_key:pem_decode(KeyData),
public_key:pem_entry_decode(PemEntry) public_key:pem_entry_decode(PemEntry)