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

@ -14,7 +14,7 @@
"If you already have a paid license, please apply it now.\n" "If you already have a paid license, please apply it now.\n"
"Or you could visit https://emqx.com/apply-licenses/emqx to get a trial license.\n" "Or you could visit https://emqx.com/apply-licenses/emqx to get a trial license.\n"
"===============================================================================\n" "===============================================================================\n"
). ).
-define(EXPIRY_LOG, -define(EXPIRY_LOG,
"\n" "\n"
@ -23,7 +23,7 @@
"Please visit https://emqx.com/apply-licenses/emqx or\n" "Please visit https://emqx.com/apply-licenses/emqx or\n"
"contact customer services.\n" "contact customer services.\n"
"======================================================\n" "======================================================\n"
). ).
-define(OFFICIAL, 1). -define(OFFICIAL, 1).
-define(TRIAL, 0). -define(TRIAL, 0).

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([
post_config_update/5 pre_config_update/3,
]). post_config_update/5
]).
-export([load/0, -export([
check/2, load/0,
unload/0, check/2,
read_license/0, unload/0,
read_license/1, read_license/0,
update_file/1, read_license/1,
update_key/1]). update_file/1,
update_key/1
]).
-define(CONF_KEY_PATH, [license]). -define(CONF_KEY_PATH, [license]).
@ -50,18 +53,20 @@ unload() ->
{ok, emqx_config:update_result()} | {error, emqx_config:update_error()}. {ok, emqx_config:update_result()} | {error, emqx_config:update_error()}.
update_file(Filename) when is_binary(Filename); is_list(Filename) -> 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()) ->
{ok, emqx_config:update_result()} | {error, emqx_config:update_error()}. {ok, emqx_config:update_result()} | {error, emqx_config:update_error()}.
update_key(Value) when is_binary(Value); is_list(Value) -> 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

@ -10,20 +10,24 @@
-behaviour(gen_server). -behaviour(gen_server).
-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/2, start_link/1,
update/1, start_link/2,
dump/0, update/1,
purge/0, dump/0,
limits/0]). purge/0,
limits/0
]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, -export([
handle_call/3, init/1,
handle_cast/2, handle_call/3,
handle_info/2]). handle_cast/2,
handle_info/2
]).
-define(LICENSE_TAB, emqx_license). -define(LICENSE_TAB, emqx_license).
@ -55,7 +59,7 @@ limits() ->
[{limits, Limits}] -> {ok, Limits}; [{limits, Limits}] -> {ok, Limits};
_ -> {error, no_license} _ -> {error, no_license}
catch catch
error : badarg -> error:badarg ->
{error, no_license} {error, no_license}
end. end.
@ -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.
@ -151,16 +161,19 @@ days_left(License) ->
{DateNow, _} = calendar:universal_time(), {DateNow, _} = calendar:universal_time(),
calendar:date_to_gregorian_days(DateEnd) - calendar:date_to_gregorian_days(DateNow). calendar:date_to_gregorian_days(DateEnd) - calendar:date_to_gregorian_days(DateNow).
need_restrict(License, DaysLeft)-> 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(
?PRINT("~-16s: ~s~n", [K, V]); fun
({K, V}) -> ({K, V}) when is_binary(V); is_atom(V); is_list(V) ->
?PRINT("~-16s: ~p~n", [K, V]) ?PRINT("~-16s: ~s~n", [K, V]);
end, emqx_license_checker:dump()); ({K, V}) ->
?PRINT("~-16s: ~p~n", [K, V])
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 update License", "Update license given as a string"} {"license reload [<File>]",
]). "Reload license from a file specified with an absolute path"},
{"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([
handle_call/3, init/1,
handle_cast/2, handle_call/3,
handle_info/2]). handle_cast/2,
handle_info/2
]).
-define(NAME, emqx). -define(NAME, emqx).
-define(INTERVAL, 5000). -define(INTERVAL, 5000).
@ -35,11 +39,12 @@ start_link(Name, ServerName, Interval, Callback) ->
init([Name, Interval, Callback]) -> init([Name, Interval, Callback]) ->
Pid = whereis(Name), Pid = whereis(Name),
State = #{interval => Interval, State = #{
name => Name, interval => Interval,
pid => Pid, name => Name,
callback => Callback pid => Pid,
}, callback => Callback
},
{ok, ensure_timer(State)}. {ok, ensure_timer(State)}.
handle_call(_Req, _From, State) -> handle_call(_Req, _From, State) ->
@ -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,40 +9,45 @@
-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([
customer_type/0, license_data/0,
license_type/0, customer_type/0,
license/0]). license_type/0,
license/0
]).
-export([
-export([parse/1, parse/1,
parse/2, 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
]). ]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Behaviour %% Behaviour
@ -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} ->
@ -107,6 +111,6 @@ do_parse(Content, Key, [Module | Modules], Errors) ->
{error, Error} -> {error, Error} ->
do_parse(Content, Key, Modules, [{Module, Error} | Errors]) do_parse(Content, Key, Modules, [{Module, Error} | Errors])
catch catch
_Class : Error : Stacktrace -> _Class:Error:Stacktrace ->
do_parse(Content, Key, Modules, [{Module, {Error, Stacktrace}} | Errors]) do_parse(Content, Key, Modules, [{Module, {Error, Stacktrace}} | Errors])
end. end.

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([
dump/1, parse/2,
customer_type/1, dump/1,
license_type/1, customer_type/1,
expiry_date/1, license_type/1,
max_connections/1]). expiry_date/1,
max_connections/1
]).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% API %% API
@ -40,25 +42,30 @@ parse(Content, Key) ->
{error, Reason} {error, Reason}
end. end.
dump(#{type := Type, dump(
customer_type := CType, #{
customer := Customer, type := Type,
email := Email, customer_type := CType,
date_start := DateStart, customer := Customer,
max_connections := MaxConns} = License) -> email := Email,
date_start := DateStart,
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}, [
{email, Email}, {customer, Customer},
{max_connections, MaxConns}, {email, Email},
{start_at, format_date(DateStart)}, {max_connections, MaxConns},
{expiry_at, format_date(DateExpiry)}, {start_at, format_date(DateStart)},
{type, format_type(Type)}, {expiry_at, format_date(DateExpiry)},
{customer_type, CType}, {type, format_type(Type)},
{expiry, Expiry}]. {customer_type, CType},
{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.
@ -82,7 +90,7 @@ do_parse(Content) ->
Signature = base64:decode(EncodedSignature), Signature = base64:decode(EncodedSignature),
{ok, {Payload, Signature}} {ok, {Payload, Signature}}
catch catch
_ : _ -> _:_ ->
{error, bad_license_format} {error, bad_license_format}
end. end.
@ -91,17 +99,20 @@ 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([
{customer_type, parse_customer_type(CType)}, {type, parse_type(Type)},
{customer, {ok, Customer}}, {customer_type, parse_customer_type(CType)},
{email, {ok, Email}}, {customer, {ok, Customer}},
{date_start, parse_date_start(DateStart)}, {email, {ok, Email}},
{days, parse_days(Days)}, {date_start, parse_date_start(DateStart)},
{max_connections, parse_max_connections(MaxConns)}]); {days, parse_days(Days)},
{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, Value} | FieldValues], Errors}; ({Name, {ok, Value}}, {FieldValues, Errors}) ->
({Name, {error, Reason}}, {FieldValues, Errors}) -> {[{Name, Value} | FieldValues], Errors};
{FieldValues, [{Name, Reason} | Errors]} ({Name, {error, Reason}}, {FieldValues, Errors}) ->
end, {FieldValues, [{Name, Reason} | Errors]}
{[], []}, end,
Fields), {[], []},
Fields
),
case Collected of case Collected of
{FieldValues, []} -> {FieldValues, []} ->
{ok, maps:from_list(FieldValues)}; {ok, maps:from_list(FieldValues)};
@ -171,9 +184,11 @@ collect_fields(Fields) ->
format_date({Year, Month, Day}) -> 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/1, start_link/0,
local_connection_count/0, start_link/1,
connection_count/0]). local_connection_count/0,
connection_count/0
]).
%% gen_server callbacks %% gen_server callbacks
-export([init/1, -export([
handle_call/3, init/1,
handle_cast/2, handle_call/3,
handle_info/2, handle_cast/2,
terminate/2, handle_info/2,
code_change/3]). terminate/2,
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,46 +14,66 @@
-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, #{
desc => "Configure the license as a string" type => string(),
}} %% so it's not logged
| common_fields()]; sensitive => true,
desc => "Configure the license as a string"
}}
| common_fields()
];
fields(file_license) -> fields(file_license) ->
[ {file, #{type => string(), [
desc => "Path to the license file" {file, #{
}} type => string(),
| common_fields()]. desc => "Path to the license file"
}}
| 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 => ""
}} }}
]. ].
validations() -> validations() ->
[ {check_license_watermark, fun check_license_watermark/1}]. [{check_license_watermark, fun check_license_watermark/1}].
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

@ -13,31 +13,43 @@
-export([init/1]). -export([init/1]).
start_link() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) -> init([]) ->
{ok, {#{strategy => one_for_one, {ok,
intensity => 10, {
period => 100}, #{
strategy => one_for_one,
intensity => 10,
period => 100
},
[#{id => license_checker, [
start => {emqx_license_checker, start_link, [fun emqx_license:read_license/0]}, #{
restart => permanent, id => license_checker,
shutdown => 5000, start => {emqx_license_checker, start_link, [fun emqx_license:read_license/0]},
type => worker, restart => permanent,
modules => [emqx_license_checker]}, shutdown => 5000,
type => worker,
modules => [emqx_license_checker]
},
#{id => license_resources, #{
start => {emqx_license_resources, start_link, []}, id => license_resources,
restart => permanent, start => {emqx_license_resources, start_link, []},
shutdown => 5000, restart => permanent,
type => worker, shutdown => 5000,
modules => [emqx_license_resources]}, type => worker,
modules => [emqx_license_resources]
},
#{id => license_installer, #{
start => {emqx_license_installer, start_link, [fun emqx_license:load/0]}, id => license_installer,
restart => permanent, start => {emqx_license_installer, start_link, [fun emqx_license:load/0]},
shutdown => 5000, restart => permanent,
type => worker, shutdown => 5000,
modules => [emqx_license_installer]} type => worker,
]}}. 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
@ -60,101 +60,125 @@ 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", [
"0", "220111",
"10", "0",
"Foo", "10",
"contact@foo.com", "Foo",
"20220111", "contact@foo.com",
"100000", "20220111",
"10"]), "100000",
"10"
]
),
#{} = emqx_license_checker:update(License), #{} = emqx_license_checker:update(License),
ok = lists:foreach( ok = lists:foreach(
fun(_) -> fun(_) ->
{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", [
"0", "220111",
"10", "0",
"Foo", "10",
"contact@foo.com", "Foo",
"20220111", "contact@foo.com",
"100000", "20220111",
"10"]), "100000",
"10"
]
),
#{} = emqx_license_checker:update(License), #{} = emqx_license_checker:update(License),
ok = lists:foreach( ok = lists:foreach(
fun(_) -> fun(_) ->
{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
"Foo", "1",
"contact@foo.com", %% Small customer
"20211101", %% Expired long ago "0",
"10", "Foo",
"10"]), "contact@foo.com",
%% Expired long ago
"20211101",
"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
@ -163,6 +187,7 @@ t_check_not_loaded(_Config) ->
mk_license(Fields) -> 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,139 +49,172 @@ t_default_limits(_Config) ->
t_dump(_Config) -> t_dump(_Config) ->
License = mk_license( License = mk_license(
["220111", [
"0", "220111",
"10", "0",
"Foo", "10",
"contact@foo.com", "Foo",
"20220111", "contact@foo.com",
"100000", "20220111",
"10"]), "100000",
"10"
]
),
#{} = emqx_license_checker:update(License), #{} = emqx_license_checker:update(License),
?assertEqual( ?assertEqual(
[{customer,<<"Foo">>}, [
{email,<<"contact@foo.com">>}, {customer, <<"Foo">>},
{max_connections,10}, {email, <<"contact@foo.com">>},
{start_at,<<"2022-01-11">>}, {max_connections, 10},
{expiry_at,<<"2295-10-27">>}, {start_at, <<"2022-01-11">>},
{type,<<"trial">>}, {expiry_at, <<"2295-10-27">>},
{customer_type,10}, {type, <<"trial">>},
{expiry,false}], {customer_type, 10},
emqx_license_checker:dump()). {expiry, false}
],
emqx_license_checker:dump()
).
t_update(_Config) -> t_update(_Config) ->
License = mk_license( License = mk_license(
["220111", [
"0", "220111",
"10", "0",
"Foo", "10",
"contact@foo.com", "Foo",
"20220111", "contact@foo.com",
"100000", "20220111",
"123"]), "100000",
"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(
begin begin
?wait_async_action( ?wait_async_action(
begin begin
erlang:send( erlang:send(
emqx_license_checker, emqx_license_checker,
check_license) check_license
end, )
#{?snk_kind := emqx_license_checked}, end,
1000) #{?snk_kind := emqx_license_checked},
end, 1000
fun(Trace) -> )
end,
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", [
"0", "220111",
"10", "0",
"Foo", "10",
"contact@foo.com", "Foo",
format_date(Date10DaysAgo), "contact@foo.com",
"1", format_date(Date10DaysAgo),
"123"]), "1",
"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", [
"1", "220111",
"0", "1",
"Foo", "0",
"contact@foo.com", "Foo",
format_date(Date100DaysAgo), "contact@foo.com",
"1", format_date(Date100DaysAgo),
"123"]), "1",
"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", [
"1", "220111",
"1", "1",
"Foo", "1",
"contact@foo.com", "Foo",
format_date(Date100DaysAgo), "contact@foo.com",
"1", format_date(Date100DaysAgo),
"123"]), "1",
"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", [
"1", "220111",
"0", "1",
"Foo", "0",
"contact@foo.com", "Foo",
format_date(Date10DaysAgo), "contact@foo.com",
"1", format_date(Date10DaysAgo),
"123"]), "1",
"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),
@ -197,12 +228,15 @@ t_unknown_calls(_Config) ->
mk_license(Fields) -> 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
@ -42,38 +42,46 @@ set_special_configs(_) -> ok.
t_update(_Config) -> t_update(_Config) ->
?check_trace( ?check_trace(
begin begin
?wait_async_action( ?wait_async_action(
begin begin
Pid0 = spawn_link(fun() -> receive exit -> ok end end), Pid0 = spawn_link(fun() ->
register(installer_test, Pid0), receive
exit -> ok
end
end),
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(
#{?snk_kind := emqx_license_installer_nochange},
100
),
{ok, _} = ?block_until( Pid0 ! exit,
#{?snk_kind := emqx_license_installer_nochange},
100),
Pid0 ! exit, {ok, _} = ?block_until(
#{?snk_kind := emqx_license_installer_noproc},
100
),
{ok, _} = ?block_until( Pid1 = spawn_link(fun() -> timer:sleep(100) end),
#{?snk_kind := emqx_license_installer_noproc}, register(installer_test, Pid1)
100), end,
#{?snk_kind := emqx_license_installer_called},
Pid1 = spawn_link(fun() -> timer:sleep(100) end), 1000
register(installer_test, Pid1) )
end, end,
#{?snk_kind := emqx_license_installer_called}, fun(Trace) ->
1000)
end,
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,135 +44,170 @@ 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",
"contact@foo.com", "contact@foo.com",
"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",
"contact@foo.com", "Bar",
"20220111", "contact@foo.com",
"100000", "20220111",
"10" "100000",
]), "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_test_lib:make_license( ]},
["220111", emqx_license_parser:parse(
"zero", emqx_license_test_lib:make_license(
"ten", [
"Foo", "220111",
"contact@foo.com", "zero",
"20220231", "ten",
"-10", "Foo",
"10" "contact@foo.com",
]), "20220231",
public_key_pem())), "-10",
"10"
]
),
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_test_lib:make_license( ]},
["220111", emqx_license_parser:parse(
"zero", emqx_license_test_lib:make_license(
"ten", [
"Foo", "220111",
"contact@foo.com", "zero",
"2022-02-1st", "ten",
"-10", "Foo",
"10" "contact@foo.com",
]), "2022-02-1st",
public_key_pem())), "-10",
"10"
]
),
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", [
"0", "220111",
"10", "0",
"Foo", "10",
"contact@foo.com", "Foo",
"20220111", "contact@foo.com",
"100000", "20220111",
"10"]), "100000",
<<".">>), "10"
]
),
<<".">>
),
[_, SignaturePart] = binary:split( [_, SignaturePart] = binary:split(
emqx_license_test_lib:make_license( emqx_license_test_lib:make_license(
["220111", [
"1", "220111",
"10", "1",
"Foo", "10",
"contact@foo.com", "Foo",
"20220111", "contact@foo.com",
"100000", "20220111",
"10"]), "100000",
<<".">>), "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">>}, [
{email,<<"contact@foo.com">>}, {customer, <<"Foo">>},
{max_connections,10}, {email, <<"contact@foo.com">>},
{start_at,<<"2022-01-11">>}, {max_connections, 10},
{expiry_at,<<"2295-10-27">>}, {start_at, <<"2022-01-11">>},
{type,<<"trial">>}, {expiry_at, <<"2295-10-27">>},
{customer_type,10}, {type, <<"trial">>},
{expiry,false}], {customer_type, 10},
emqx_license_parser:dump(License)). {expiry, false}
],
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()),
@ -192,7 +227,7 @@ t_max_connections(_Config) ->
t_expiry_date(_Config) -> t_expiry_date(_Config) ->
{ok, License} = emqx_license_parser:parse(sample_license(), public_key_pem()), {ok, License} = emqx_license_parser:parse(sample_license(), public_key_pem()),
?assertEqual({2295,10,27}, emqx_license_parser:expiry_date(License)). ?assertEqual({2295, 10, 27}, emqx_license_parser:expiry_date(License)).
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
%% Helpers %% Helpers
@ -203,11 +238,14 @@ public_key_pem() ->
sample_license() -> sample_license() ->
emqx_license_test_lib:make_license( emqx_license_test_lib:make_license(
["220111", [
"0", "220111",
"10", "0",
"Foo", "10",
"contact@foo.com", "Foo",
"20220111", "contact@foo.com",
"100,000", "20220111",
"10"]). "100,000",
"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
@ -42,41 +42,45 @@ set_special_configs(_) -> ok.
t_connection_count(_Config) -> t_connection_count(_Config) ->
?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() ),
end, emqx_license_resources:connection_count()
fun(ConnCount, Trace) -> end,
?assertEqual(0, ConnCount), fun(ConnCount, Trace) ->
?assertMatch([_ | _], ?of_kind(emqx_license_resources_updated, Trace)) ?assertEqual(0, ConnCount),
end), ?assertMatch([_ | _], ?of_kind(emqx_license_resources_updated, Trace))
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),
meck:new(emqx_license_proto_v1, [passthrough]), meck:new(emqx_license_proto_v1, [passthrough]),
meck:expect( meck:expect(
emqx_license_proto_v1, emqx_license_proto_v1,
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() ),
end, emqx_license_resources:connection_count()
fun(ConnCount, _Trace) -> end,
?assertEqual(15, ConnCount) fun(ConnCount, _Trace) ->
end), ?assertEqual(15, ConnCount)
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)