186 lines
6.1 KiB
Erlang
186 lines
6.1 KiB
Erlang
%%--------------------------------------------------------------------
|
|
%% Copyright (c) 2022-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
%%--------------------------------------------------------------------
|
|
|
|
-module(emqx_license).
|
|
|
|
-include("emqx_license.hrl").
|
|
-include_lib("emqx/include/logger.hrl").
|
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
|
-include_lib("typerefl/include/types.hrl").
|
|
|
|
-behaviour(emqx_config_handler).
|
|
-behaviour(emqx_config_backup).
|
|
|
|
-export([
|
|
pre_config_update/3,
|
|
post_config_update/5
|
|
]).
|
|
|
|
-export([
|
|
load/0,
|
|
check/2,
|
|
unload/0,
|
|
read_license/0,
|
|
read_license/1,
|
|
update_key/1,
|
|
update_setting/1
|
|
]).
|
|
|
|
-export([import_config/1]).
|
|
|
|
-define(CONF_KEY_PATH, [license]).
|
|
|
|
%% Give the license app the highest priority.
|
|
%% We don't define it in the emqx_hooks.hrl becasue that is an opensource code
|
|
%% and can be changed by the communitiy.
|
|
-define(HP_LICENSE, 2000).
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% API
|
|
%%------------------------------------------------------------------------------
|
|
|
|
-spec read_license() -> {ok, emqx_license_parser:license()} | {error, term()}.
|
|
read_license() ->
|
|
read_license(emqx:get_config(?CONF_KEY_PATH)).
|
|
|
|
-spec load() -> ok.
|
|
load() ->
|
|
emqx_license_cli:load(),
|
|
emqx_conf:add_handler(?CONF_KEY_PATH, ?MODULE),
|
|
add_license_hook().
|
|
|
|
-spec unload() -> ok.
|
|
unload() ->
|
|
%% Delete the hook. This means that if the user calls
|
|
%% `application:stop(emqx_license).` from the shell, then here should no limitations!
|
|
del_license_hook(),
|
|
emqx_conf:remove_handler(?CONF_KEY_PATH),
|
|
emqx_license_cli:unload().
|
|
|
|
-spec update_key(binary() | string()) ->
|
|
{ok, emqx_config:update_result()} | {error, emqx_config:update_error()}.
|
|
update_key(Value) when is_binary(Value); is_list(Value) ->
|
|
Result = exec_config_update({key, Value}),
|
|
handle_config_update_result(Result).
|
|
|
|
update_setting(Setting) when is_map(Setting) ->
|
|
Result = exec_config_update({setting, Setting}),
|
|
handle_config_update_result(Result).
|
|
|
|
exec_config_update(Param) ->
|
|
emqx_conf:update(
|
|
?CONF_KEY_PATH,
|
|
Param,
|
|
#{rawconf_with_defaults => true, override_to => cluster}
|
|
).
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% emqx_hooks
|
|
%%------------------------------------------------------------------------------
|
|
|
|
check(_ConnInfo, AckProps) ->
|
|
case emqx_license_checker:limits() of
|
|
{ok, #{max_connections := ?ERR_EXPIRED}} ->
|
|
?SLOG(error, #{msg => "connection_rejected_due_to_license_expired"}, #{tag => "LICENSE"}),
|
|
{stop, {error, ?RC_QUOTA_EXCEEDED}};
|
|
{ok, #{max_connections := MaxClients}} ->
|
|
case check_max_clients_exceeded(MaxClients) of
|
|
true ->
|
|
?SLOG_THROTTLE(
|
|
error,
|
|
#{msg => connection_rejected_due_to_license_limit_reached},
|
|
#{tag => "LICENSE"}
|
|
),
|
|
{stop, {error, ?RC_QUOTA_EXCEEDED}};
|
|
false ->
|
|
{ok, AckProps}
|
|
end;
|
|
{error, Reason} ->
|
|
?SLOG(
|
|
error,
|
|
#{
|
|
msg => "connection_rejected_due_to_license_not_loaded",
|
|
reason => Reason
|
|
},
|
|
#{tag => "LICENSE"}
|
|
),
|
|
{stop, {error, ?RC_QUOTA_EXCEEDED}}
|
|
end.
|
|
|
|
import_config(#{<<"license">> := Config}) ->
|
|
OldConf = emqx:get_config(?CONF_KEY_PATH),
|
|
case exec_config_update(Config) of
|
|
{ok, #{config := NewConf}} ->
|
|
Changed = maps:get(changed, emqx_utils_maps:diff_maps(NewConf, OldConf)),
|
|
Changed1 = lists:map(fun(Key) -> [license, Key] end, maps:keys(Changed)),
|
|
{ok, #{root_key => license, changed => Changed1}};
|
|
Error ->
|
|
{error, #{root_key => license, reason => Error}}
|
|
end;
|
|
import_config(_RawConf) ->
|
|
{ok, #{root_key => license, changed => []}}.
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% emqx_config_handler callbacks
|
|
%%------------------------------------------------------------------------------
|
|
|
|
pre_config_update(_, Cmd, Conf) ->
|
|
{ok, do_update(Cmd, Conf)}.
|
|
|
|
post_config_update(_Path, {setting, _}, NewConf, _Old, _AppEnvs) ->
|
|
{ok, NewConf};
|
|
post_config_update(_Path, _Cmd, NewConf, _Old, _AppEnvs) ->
|
|
case read_license(NewConf) of
|
|
{ok, License} ->
|
|
{ok, emqx_license_checker:update(License)};
|
|
{error, _} = Error ->
|
|
Error
|
|
end.
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% Private functions
|
|
%%------------------------------------------------------------------------------
|
|
|
|
add_license_hook() ->
|
|
ok = emqx_hooks:put('client.connect', {?MODULE, check, []}, ?HP_LICENSE).
|
|
|
|
del_license_hook() ->
|
|
_ = emqx_hooks:del('client.connect', {?MODULE, check, []}),
|
|
ok.
|
|
|
|
do_update({key, Content}, Conf) when is_binary(Content); is_list(Content) ->
|
|
case emqx_license_parser:parse(Content) of
|
|
{ok, _License} ->
|
|
Conf#{<<"key">> => Content};
|
|
{error, Reason} ->
|
|
erlang:throw(Reason)
|
|
end;
|
|
do_update({setting, Setting0}, Conf) ->
|
|
#{<<"key">> := Key} = Conf,
|
|
%% only allow updating dynamic_max_connections when it's BUSINESS_CRITICAL
|
|
Setting =
|
|
case emqx_license_parser:is_business_critical(Key) of
|
|
true ->
|
|
Setting0;
|
|
false ->
|
|
maps:without([<<"dynamic_max_connections">>], Setting0)
|
|
end,
|
|
maps:merge(Conf, Setting);
|
|
do_update(NewConf, _PrevConf) ->
|
|
#{<<"key">> := NewKey} = NewConf,
|
|
do_update({key, NewKey}, NewConf).
|
|
|
|
check_max_clients_exceeded(MaxClients) ->
|
|
emqx_license_resources:connection_count() > MaxClients * 1.1.
|
|
|
|
read_license(#{key := Content}) ->
|
|
emqx_license_parser:parse(Content).
|
|
|
|
handle_config_update_result({error, {post_config_update, ?MODULE, Error}}) ->
|
|
{error, Error};
|
|
handle_config_update_result({error, _} = Error) ->
|
|
Error;
|
|
handle_config_update_result({ok, #{post_config_update := #{emqx_license := Result}}}) ->
|
|
{ok, Result}.
|