140 lines
4.6 KiB
Erlang
140 lines
4.6 KiB
Erlang
%%--------------------------------------------------------------------
|
|
%% Copyright (c) 2022 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).
|
|
|
|
-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
|
|
]).
|
|
|
|
-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 = emqx_conf:update(
|
|
?CONF_KEY_PATH,
|
|
{key, Value},
|
|
#{rawconf_with_defaults => true, override_to => cluster}
|
|
),
|
|
handle_config_update_result(Result).
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% 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"}),
|
|
{stop, {error, ?RC_QUOTA_EXCEEDED}};
|
|
{ok, #{max_connections := MaxClients}} ->
|
|
case check_max_clients_exceeded(MaxClients) of
|
|
true ->
|
|
?SLOG(error, #{msg => "connection_rejected_due_to_license_limit_reached"}),
|
|
{stop, {error, ?RC_QUOTA_EXCEEDED}};
|
|
false ->
|
|
{ok, AckProps}
|
|
end;
|
|
{error, Reason} ->
|
|
?SLOG(error, #{
|
|
msg => "connection_rejected_due_to_license_not_loaded",
|
|
reason => Reason
|
|
}),
|
|
{stop, {error, ?RC_QUOTA_EXCEEDED}}
|
|
end.
|
|
|
|
%%------------------------------------------------------------------------------
|
|
%% emqx_config_handler callbacks
|
|
%%------------------------------------------------------------------------------
|
|
|
|
pre_config_update(_, Cmd, Conf) ->
|
|
{ok, do_update(Cmd, Conf)}.
|
|
|
|
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;
|
|
%% We don't do extra action when update license's watermark.
|
|
do_update(_Other, Conf) ->
|
|
Conf.
|
|
|
|
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}.
|