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"}}]}.
{project_plugins, [erlfmt]}.

View File

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

View File

@ -10,17 +10,20 @@
-behaviour(emqx_config_handler).
-export([pre_config_update/3,
-export([
pre_config_update/3,
post_config_update/5
]).
-export([load/0,
-export([
load/0,
check/2,
unload/0,
read_license/0,
read_license/1,
update_file/1,
update_key/1]).
update_key/1
]).
-define(CONF_KEY_PATH, [license]).
@ -52,7 +55,8 @@ update_file(Filename) when is_binary(Filename); is_list(Filename) ->
Result = emqx_conf:update(
?CONF_KEY_PATH,
{file, Filename},
#{rawconf_with_defaults => true, override_to => local}),
#{rawconf_with_defaults => true, override_to => local}
),
handle_config_update_result(Result).
-spec update_key(binary() | string()) ->
@ -61,7 +65,8 @@ update_key(Value) when is_binary(Value); is_list(Value) ->
Result = emqx_conf:update(
?CONF_KEY_PATH,
{key, Value},
#{rawconf_with_defaults => true, override_to => cluster}),
#{rawconf_with_defaults => true, override_to => cluster}
),
handle_config_update_result(Result).
%%------------------------------------------------------------------------------
@ -82,8 +87,10 @@ check(_ConnInfo, AckProps) ->
{ok, AckProps}
end;
{error, Reason} ->
?SLOG(error, #{msg => "connection_rejected_due_to_license_not_loaded",
reason => Reason}),
?SLOG(error, #{
msg => "connection_rejected_due_to_license_not_loaded",
reason => Reason
}),
{stop, {error, ?RC_QUOTA_EXCEEDED}}
end.
@ -98,7 +105,8 @@ post_config_update(_Path, _Cmd, NewConf, _Old, _AppEnvs) ->
case read_license(NewConf) of
{ok, License} ->
{ok, emqx_license_checker:update(License)};
{error, _} = Error -> Error
{error, _} = Error ->
Error
end.
%%------------------------------------------------------------------------------
@ -124,7 +132,6 @@ do_update({file, Filename}, Conf) ->
{error, Reason} ->
erlang:throw({invalid_license_file, Reason})
end;
do_update({key, Content}, Conf) when is_binary(Content); is_list(Content) ->
case emqx_license_parser:parse(Content) of
{ok, _License} ->
@ -144,9 +151,10 @@ read_license(#{file := Filename}) ->
{ok, Content} -> emqx_license_parser:parse(Content);
{error, _} = Error -> Error
end;
read_license(#{key := Content}) ->
emqx_license_parser:parse(Content).
handle_config_update_result({error, _} = Error) -> Error;
handle_config_update_result({ok, #{post_config_update := #{emqx_license := Result}}}) -> {ok, Result}.
handle_config_update_result({error, _} = Error) ->
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(EXPIRY_ALARM_CHECK_INTERVAL, 24 * 60 * 60).
-export([start_link/1,
-export([
start_link/1,
start_link/2,
update/1,
dump/0,
purge/0,
limits/0]).
limits/0
]).
%% gen_server callbacks
-export([init/1,
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2]).
handle_info/2
]).
-define(LICENSE_TAB, emqx_license).
@ -71,10 +75,14 @@ purge() ->
init([LicenseFetcher, CheckInterval]) ->
case LicenseFetcher() of
{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),
State0 = ensure_check_license_timer(#{check_license_interval => CheckInterval,
license => License}),
State0 = ensure_check_license_timer(#{
check_license_interval => CheckInterval,
license => License
}),
State = ensure_check_expiry_timer(State0),
{ok, State};
{error, Reason} ->
@ -100,12 +108,10 @@ handle_info(check_license, #{license := License} = State) ->
NewState = ensure_check_license_timer(State),
?tp(debug, emqx_license_checked, #{}),
{noreply, NewState};
handle_info(check_expiry_alarm, #{license := License} = State) ->
_ = expiry_early_alarm(License),
NewState = ensure_check_expiry_timer(State),
{noreply, NewState};
handle_info(_Msg, State) ->
{noreply, State}.
@ -123,7 +129,8 @@ ensure_check_expiry_timer(State) ->
State#{expiry_alarm_timer => Ref}.
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
end,
@ -134,12 +141,15 @@ check_license(License) ->
NeedRestrict = need_restrict(License, DaysLeft),
Limits = limits(License, NeedRestrict),
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) ->
emqx_license_parser:customer_type(License) == ?EVALUATION_CUSTOMER;
warn_evaluation(_License, _NeedRestrict) -> false.
warn_evaluation(_License, _NeedRestrict) ->
false.
warn_expiry(_License, NeedRestrict) -> NeedRestrict.
@ -155,12 +165,15 @@ need_restrict(License, DaysLeft)->
CType = emqx_license_parser:customer_type(License),
Type = emqx_license_parser:license_type(License),
DaysLeft < 0
andalso (Type =/= ?OFFICIAL) orelse small_customer_over_expired(CType, DaysLeft).
DaysLeft < 0 andalso
(Type =/= ?OFFICIAL) orelse small_customer_over_expired(CType, DaysLeft).
small_customer_over_expired(?SMALL_CUSTOMER, DaysLeft)
when DaysLeft < ?EXPIRED_DAY -> true;
small_customer_over_expired(_CType, _DaysLeft) -> false.
small_customer_over_expired(?SMALL_CUSTOMER, DaysLeft) when
DaysLeft < ?EXPIRED_DAY
->
true;
small_customer_over_expired(_CType, _DaysLeft) ->
false.
apply_limits(Limits) ->
ets:insert(?LICENSE_TAB, {limits, Limits}).

View File

@ -26,36 +26,41 @@ license(["reload"]) ->
#{key := _Key} ->
?PRINT_MSG("License is not configured as a file, please specify file explicitly~n")
end;
license(["reload", Filename]) ->
case emqx_license:update_file(Filename) of
{ok, Warnings} ->
ok = print_warnings(Warnings),
ok = ?PRINT_MSG("ok~n");
{error, Reason} -> ?PRINT("Error: ~p~n", [Reason])
{error, Reason} ->
?PRINT("Error: ~p~n", [Reason])
end;
license(["update", EncodedLicense]) ->
case emqx_license:update_key(EncodedLicense) of
{ok, Warnings} ->
ok = print_warnings(Warnings),
ok = ?PRINT_MSG("ok~n");
{error, Reason} -> ?PRINT("Error: ~p~n", [Reason])
{error, Reason} ->
?PRINT("Error: ~p~n", [Reason])
end;
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]);
({K, V}) ->
?PRINT("~-16s: ~p~n", [K, V])
end, emqx_license_checker:dump());
end,
emqx_license_checker:dump()
);
license(_) ->
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"}
]).
]
).
unload() ->
ok = emqx_ctl:unregister_command(license).
@ -70,8 +75,10 @@ print_warnings(Warnings) ->
print_evaluation_warning(#{warn_evaluation := true}) ->
?PRINT_MSG(?EVALUATION_LOG);
print_evaluation_warning(_) -> ok.
print_evaluation_warning(_) ->
ok.
print_expiry_warning(#{warn_expiry := true}) ->
?PRINT_MSG(?EXPIRY_LOG);
print_expiry_warning(_) -> ok.
print_expiry_warning(_) ->
ok.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,28 +16,40 @@ start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
{ok, {#{strategy => one_for_one,
{ok,
{
#{
strategy => one_for_one,
intensity => 10,
period => 100},
period => 100
},
[#{id => license_checker,
[
#{
id => license_checker,
start => {emqx_license_checker, start_link, [fun emqx_license:read_license/0]},
restart => permanent,
shutdown => 5000,
type => worker,
modules => [emqx_license_checker]},
modules => [emqx_license_checker]
},
#{id => license_resources,
#{
id => license_resources,
start => {emqx_license_resources, start_link, []},
restart => permanent,
shutdown => 5000,
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]},
restart => permanent,
shutdown => 5000,
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),
RawConfig = #{<<"file">> => emqx_license_test_lib:default_license()},
emqx_config:put_raw([<<"license">>], RawConfig);
set_special_configs(_) -> ok.
set_special_configs(_) ->
ok.
%%------------------------------------------------------------------------------
%% Tests
@ -61,43 +61,52 @@ set_special_configs(_) -> ok.
t_update_file(_Config) ->
?assertMatch(
{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">>),
?assertMatch(
{error, [_ | _]},
emqx_license:update_file("license_with_invalid_content.lic")),
emqx_license:update_file("license_with_invalid_content.lic")
),
?assertMatch(
{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) ->
?assertMatch(
{error, [_ | _]},
emqx_license:update_key("invalid.license")),
emqx_license:update_key("invalid.license")
),
{ok, LicenseValue} = file:read_file(emqx_license_test_lib:default_license()),
?assertMatch(
{ok, #{}},
emqx_license:update_key(LicenseValue)).
emqx_license:update_key(LicenseValue)
).
t_read_license_from_invalid_file(_Config) ->
?assertMatch(
{error, enoent},
emqx_license:read_license()).
emqx_license:read_license()
).
t_check_exceeded(_Config) ->
License = mk_license(
["220111",
[
"220111",
"0",
"10",
"Foo",
"contact@foo.com",
"20220111",
"100000",
"10"]),
"10"
]
),
#{} = emqx_license_checker:update(License),
ok = lists:foreach(
@ -105,22 +114,27 @@ t_check_exceeded(_Config) ->
{ok, C} = emqtt:start_link(),
{ok, _} = emqtt:connect(C)
end,
lists:seq(1, 12)),
lists:seq(1, 12)
),
?assertEqual(
{stop, {error, ?RC_QUOTA_EXCEEDED}},
emqx_license:check(#{}, #{})).
emqx_license:check(#{}, #{})
).
t_check_ok(_Config) ->
License = mk_license(
["220111",
[
"220111",
"0",
"10",
"Foo",
"contact@foo.com",
"20220111",
"100000",
"10"]),
"10"
]
),
#{} = emqx_license_checker:update(License),
ok = lists:foreach(
@ -128,33 +142,43 @@ t_check_ok(_Config) ->
{ok, C} = emqtt:start_link(),
{ok, _} = emqtt:connect(C)
end,
lists:seq(1, 11)),
lists:seq(1, 11)
),
?assertEqual(
{ok, #{}},
emqx_license:check(#{}, #{})).
emqx_license:check(#{}, #{})
).
t_check_expired(_Config) ->
License = mk_license(
["220111",
"1", %% Official customer
"0", %% Small customer
[
"220111",
%% Official customer
"1",
%% Small customer
"0",
"Foo",
"contact@foo.com",
"20211101", %% Expired long ago
%% Expired long ago
"20211101",
"10",
"10"]),
"10"
]
),
#{} = emqx_license_checker:update(License),
?assertEqual(
{stop, {error, ?RC_QUOTA_EXCEEDED}},
emqx_license:check(#{}, #{})).
emqx_license:check(#{}, #{})
).
t_check_not_loaded(_Config) ->
ok = emqx_license_checker:purge(),
?assertEqual(
{stop, {error, ?RC_QUOTA_EXCEEDED}},
emqx_license:check(#{}, #{})).
emqx_license:check(#{}, #{})
).
%%------------------------------------------------------------------------------
%% Helpers
@ -164,5 +188,6 @@ mk_license(Fields) ->
EncodedLicense = emqx_license_test_lib:make_license(Fields),
{ok, License} = emqx_license_parser:parse(
EncodedLicense,
emqx_license_test_lib:public_key_pem()),
emqx_license_test_lib:public_key_pem()
),
License.

View File

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

View File

@ -33,8 +33,8 @@ end_per_testcase(_Case, _Config) ->
set_special_configs(emqx_license) ->
Config = #{file => emqx_license_test_lib:default_license()},
emqx_config:put([license], Config);
set_special_configs(_) -> ok.
set_special_configs(_) ->
ok.
%%------------------------------------------------------------------------------
%% Tests
@ -45,35 +45,43 @@ t_update(_Config) ->
begin
?wait_async_action(
begin
Pid0 = spawn_link(fun() -> receive exit -> ok end end),
Pid0 = spawn_link(fun() ->
receive
exit -> ok
end
end),
register(installer_test, Pid0),
{ok, _} = emqx_license_installer:start_link(
installer_test,
?MODULE,
10,
fun() -> ok end),
fun() -> ok end
),
{ok, _} = ?block_until(
#{?snk_kind := emqx_license_installer_nochange},
100),
100
),
Pid0 ! exit,
{ok, _} = ?block_until(
#{?snk_kind := emqx_license_installer_noproc},
100),
100
),
Pid1 = spawn_link(fun() -> timer:sleep(100) end),
register(installer_test, Pid1)
end,
#{?snk_kind := emqx_license_installer_called},
1000)
1000
)
end,
fun(Trace) ->
?assertMatch([_ | _], ?of_kind(emqx_license_installer_called, Trace))
end).
end
).
t_unknown_calls(_Config) ->
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) ->
Config = #{file => emqx_license_test_lib:default_license()},
emqx_config:put([license], Config);
set_special_configs(_) -> ok.
set_special_configs(_) ->
ok.
%%------------------------------------------------------------------------------
%% Tests
@ -44,11 +44,11 @@ t_parse(_Config) ->
%% invalid version
?assertMatch(
{error,
[{emqx_license_parser_v20220101,invalid_version}]},
{error, [{emqx_license_parser_v20220101, invalid_version}]},
emqx_license_parser:parse(
emqx_license_test_lib:make_license(
["220101",
[
"220101",
"0",
"10",
"Foo",
@ -56,36 +56,46 @@ t_parse(_Config) ->
"20220111",
"100000",
"10"
]),
public_key_pem())),
]
),
public_key_pem()
)
),
%% invalid field number
?assertMatch(
{error,
[{emqx_license_parser_v20220101,invalid_field_number}]},
{error, [{emqx_license_parser_v20220101, invalid_field_number}]},
emqx_license_parser:parse(
emqx_license_test_lib:make_license(
["220111",
[
"220111",
"0",
"10",
"Foo", "Bar",
"Foo",
"Bar",
"contact@foo.com",
"20220111",
"100000",
"10"
]),
public_key_pem())),
]
),
public_key_pem()
)
),
?assertMatch(
{error,
[{emqx_license_parser_v20220101,
[{type,invalid_license_type},
{error, [
{emqx_license_parser_v20220101, [
{type, invalid_license_type},
{customer_type, invalid_customer_type},
{date_start, invalid_date},
{days,invalid_int_value}]}]},
{days, invalid_int_value}
]}
]},
emqx_license_parser:parse(
emqx_license_test_lib:make_license(
["220111",
[
"220111",
"zero",
"ten",
"Foo",
@ -93,19 +103,25 @@ t_parse(_Config) ->
"20220231",
"-10",
"10"
]),
public_key_pem())),
]
),
public_key_pem()
)
),
?assertMatch(
{error,
[{emqx_license_parser_v20220101,
[{type,invalid_license_type},
{error, [
{emqx_license_parser_v20220101, [
{type, invalid_license_type},
{customer_type, invalid_customer_type},
{date_start, invalid_date},
{days,invalid_int_value}]}]},
{days, invalid_int_value}
]}
]},
emqx_license_parser:parse(
emqx_license_test_lib:make_license(
["220111",
[
"220111",
"zero",
"ten",
"Foo",
@ -113,66 +129,85 @@ t_parse(_Config) ->
"2022-02-1st",
"-10",
"10"
]),
public_key_pem())),
]
),
public_key_pem()
)
),
%% invalid signature
[LicensePart, _] = binary:split(
emqx_license_test_lib:make_license(
["220111",
[
"220111",
"0",
"10",
"Foo",
"contact@foo.com",
"20220111",
"100000",
"10"]),
<<".">>),
"10"
]
),
<<".">>
),
[_, SignaturePart] = binary:split(
emqx_license_test_lib:make_license(
["220111",
[
"220111",
"1",
"10",
"Foo",
"contact@foo.com",
"20220111",
"100000",
"10"]),
<<".">>),
"10"
]
),
<<".">>
),
?assertMatch(
{error,
[{emqx_license_parser_v20220101,invalid_signature}]},
{error, [{emqx_license_parser_v20220101, invalid_signature}]},
emqx_license_parser:parse(
iolist_to_binary([LicensePart, <<".">>, SignaturePart]),
public_key_pem())),
public_key_pem()
)
),
%% totally invalid strings as license
?assertMatch(
{error, [_ | _]},
emqx_license_parser:parse(
<<"badlicense">>,
public_key_pem())),
public_key_pem()
)
),
?assertMatch(
{error, [_ | _]},
emqx_license_parser:parse(
<<"bad.license">>,
public_key_pem())).
public_key_pem()
)
).
t_dump(_Config) ->
{ok, License} = emqx_license_parser:parse(sample_license(), public_key_pem()),
?assertEqual(
[{customer,<<"Foo">>},
[
{customer, <<"Foo">>},
{email, <<"contact@foo.com">>},
{max_connections, 10},
{start_at, <<"2022-01-11">>},
{expiry_at, <<"2295-10-27">>},
{type, <<"trial">>},
{customer_type, 10},
{expiry,false}],
emqx_license_parser:dump(License)).
{expiry, false}
],
emqx_license_parser:dump(License)
).
t_customer_type(_Config) ->
{ok, License} = emqx_license_parser:parse(sample_license(), public_key_pem()),
@ -203,11 +238,14 @@ public_key_pem() ->
sample_license() ->
emqx_license_test_lib:make_license(
["220111",
[
"220111",
"0",
"10",
"Foo",
"contact@foo.com",
"20220111",
"100,000",
"10"]).
"10"
]
).

View File

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

View File

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