feat(license): allow setting 'default' license key

This commit is contained in:
Zaiming (Stone) Shi 2023-11-23 17:31:59 +01:00
parent caaf8113fc
commit 14077ec43b
8 changed files with 58 additions and 41 deletions

View File

@ -59,6 +59,12 @@
max_connections/1 max_connections/1
]). ]).
%% for testing purpose
-export([
default/0,
pubkey/0
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Behaviour %% Behaviour
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -82,19 +88,18 @@
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-ifdef(TEST).
pubkey() -> persistent_term:get(emqx_license_test_pubkey, ?PUBKEY).
-else.
pubkey() -> ?PUBKEY. pubkey() -> ?PUBKEY.
-endif. default() -> emqx_license_schema:default_license().
%% @doc Parse license key. %% @doc Parse license key.
%% If the license key is prefixed with "file://path/to/license/file", %% If the license key is prefixed with "file://path/to/license/file",
%% then the license key is read from the file. %% then the license key is read from the file.
-spec parse(string() | binary()) -> {ok, license()} | {error, map()}. -spec parse(default | string() | binary()) -> {ok, license()} | {error, map()}.
parse(Content) -> parse(Content) ->
parse(iolist_to_binary(Content), pubkey()). parse(to_bin(Content), ?MODULE:pubkey()).
parse(<<"default">>, PubKey) ->
parse(?MODULE:default(), PubKey);
parse(<<"file://", Path/binary>> = FileKey, PubKey) -> parse(<<"file://", Path/binary>> = FileKey, PubKey) ->
case file:read_file(Path) of case file:read_file(Path) of
{ok, Content} -> {ok, Content} ->
@ -159,3 +164,8 @@ do_parse(Content, Key, [Module | Modules], Errors) ->
#{module => Module, error => Error, stacktrace => Stacktrace} | Errors #{module => Module, error => Error, stacktrace => Stacktrace} | Errors
]) ])
end. end.
to_bin(A) when is_atom(A) ->
atom_to_binary(A);
to_bin(L) ->
iolist_to_binary(L).

View File

@ -38,8 +38,8 @@ tags() ->
fields(key_license) -> fields(key_license) ->
[ [
{key, #{ {key, #{
type => binary(), type => hoconsc:union([default, binary()]),
default => default_license(), default => <<"default">>,
%% so it's not logged %% so it's not logged
sensitive => true, sensitive => true,
required => true, required => true,

View File

@ -16,12 +16,14 @@ all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
init_per_suite(Config) -> init_per_suite(Config) ->
emqx_license_test_lib:mock_parser(),
_ = application:load(emqx_conf), _ = application:load(emqx_conf),
emqx_config:save_schema_mod_and_names(emqx_license_schema), emqx_config:save_schema_mod_and_names(emqx_license_schema),
emqx_common_test_helpers:start_apps([emqx_license], fun set_special_configs/1), emqx_common_test_helpers:start_apps([emqx_license], fun set_special_configs/1),
Config. Config.
end_per_suite(_) -> end_per_suite(_) ->
emqx_license_test_lib:unmock_parser(),
emqx_common_test_helpers:stop_apps([emqx_license]), emqx_common_test_helpers:stop_apps([emqx_license]),
ok. ok.
@ -103,17 +105,7 @@ setup_test(TestCase, Config) when
), ),
ok; ok;
(emqx_license) -> (emqx_license) ->
LicensePath = filename:join(emqx_license:license_dir(), "emqx.lic"), set_special_configs(emqx_license),
filelib:ensure_dir(LicensePath),
ok = file:write_file(LicensePath, LicenseKey),
LicConfig = #{type => file, file => LicensePath},
emqx_config:put([license], LicConfig),
RawConfig = #{<<"type">> => file, <<"file">> => LicensePath},
emqx_config:put_raw([<<"license">>], RawConfig),
ok = persistent_term:put(
emqx_license_test_pubkey,
emqx_license_test_lib:public_key_pem()
),
ok; ok;
(_) -> (_) ->
ok ok
@ -129,9 +121,9 @@ teardown_test(_TestCase, _Config) ->
ok. ok.
set_special_configs(emqx_license) -> set_special_configs(emqx_license) ->
Config = #{key => emqx_license_test_lib:default_license()}, Config = #{key => default},
emqx_config:put([license], Config), emqx_config:put([license], Config),
RawConfig = #{<<"key">> => emqx_license_test_lib:default_license()}, RawConfig = #{<<"key">> => <<"default">>},
emqx_config:put_raw([<<"license">>], RawConfig); emqx_config:put_raw([<<"license">>], RawConfig);
set_special_configs(_) -> set_special_configs(_) ->
ok. ok.
@ -150,7 +142,7 @@ t_update_value(_Config) ->
emqx_license:update_key("invalid.license") emqx_license:update_key("invalid.license")
), ),
LicenseValue = emqx_license_test_lib:default_license(), LicenseValue = emqx_license_test_lib:default_test_license(),
?assertMatch( ?assertMatch(
{ok, #{}}, {ok, #{}},

View File

@ -16,15 +16,12 @@ all() ->
init_per_suite(CtConfig) -> init_per_suite(CtConfig) ->
_ = application:load(emqx_conf), _ = application:load(emqx_conf),
ok = persistent_term:put( emqx_license_test_lib:mock_parser(),
emqx_license_test_pubkey,
emqx_license_test_lib:public_key_pem()
),
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),
CtConfig. CtConfig.
end_per_suite(_) -> end_per_suite(_) ->
persistent_term:erase(emqx_license_test_pubkey), emqx_license_test_lib:unmock_parser(),
ok = emqx_common_test_helpers:stop_apps([emqx_license]). ok = emqx_common_test_helpers:stop_apps([emqx_license]).
init_per_testcase(t_default_limits, Config) -> init_per_testcase(t_default_limits, Config) ->

View File

@ -24,15 +24,12 @@ end_per_suite(_) ->
ok. ok.
init_per_testcase(_Case, Config) -> init_per_testcase(_Case, Config) ->
ok = persistent_term:put( emqx_license_test_lib:mock_parser(),
emqx_license_test_pubkey,
emqx_license_test_lib:public_key_pem()
),
{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(_Case, _Config) -> end_per_testcase(_Case, _Config) ->
persistent_term:erase(emqx_license_test_pubkey), emqx_license_test_lib:unmock_parser(),
ok. ok.
set_special_configs(emqx_license) -> set_special_configs(emqx_license) ->

View File

@ -19,6 +19,7 @@ all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
init_per_suite(Config) -> init_per_suite(Config) ->
emqx_license_test_lib:mock_parser(),
_ = application:load(emqx_conf), _ = application:load(emqx_conf),
emqx_config:save_schema_mod_and_names(emqx_license_schema), emqx_config:save_schema_mod_and_names(emqx_license_schema),
emqx_common_test_helpers:start_apps([emqx_license, emqx_dashboard], fun set_special_configs/1), emqx_common_test_helpers:start_apps([emqx_license, emqx_dashboard], fun set_special_configs/1),
@ -31,7 +32,7 @@ end_per_suite(_) ->
emqx_config:put([license], Config), emqx_config:put([license], Config),
RawConfig = #{<<"key">> => LicenseKey}, RawConfig = #{<<"key">> => LicenseKey},
emqx_config:put_raw([<<"license">>], RawConfig), emqx_config:put_raw([<<"license">>], RawConfig),
persistent_term:erase(emqx_license_test_pubkey), emqx_license_test_lib:unmock_parser(),
ok. ok.
set_special_configs(emqx_dashboard) -> set_special_configs(emqx_dashboard) ->
@ -48,10 +49,6 @@ set_special_configs(emqx_license) ->
<<"connection_high_watermark">> => <<"80%">> <<"connection_high_watermark">> => <<"80%">>
}, },
emqx_config:put_raw([<<"license">>], RawConfig), emqx_config:put_raw([<<"license">>], RawConfig),
ok = persistent_term:put(
emqx_license_test_pubkey,
emqx_license_test_lib:public_key_pem()
),
ok; ok;
set_special_configs(_) -> set_special_configs(_) ->
ok. ok.
@ -113,6 +110,19 @@ t_license_info(_Config) ->
), ),
ok. ok.
t_set_default_license(_Config) ->
NewKey = <<"default">>,
Res = request(
post,
uri(["license"]),
#{key => NewKey}
),
?assertMatch({ok, 200, _}, Res),
{ok, 200, Payload} = Res,
%% assert that it's not the string "default" returned
?assertMatch(#{<<"customer">> := _}, emqx_utils_json:decode(Payload, [return_maps])),
ok.
t_license_upload_key_success(_Config) -> t_license_upload_key_success(_Config) ->
NewKey = emqx_license_test_lib:make_license(#{max_connections => "999"}), NewKey = emqx_license_test_lib:make_license(#{max_connections => "999"}),
Res = request( Res = request(

View File

@ -70,3 +70,13 @@ default_test_license() ->
default_license() -> default_license() ->
emqx_license_schema:default_license(). emqx_license_schema:default_license().
mock_parser() ->
meck:new(emqx_license_parser, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_license_parser, pubkey, fun() -> public_key_pem() end),
meck:expect(emqx_license_parser, default, fun() -> default_test_license() end),
ok.
unmock_parser() ->
meck:unload(emqx_license_parser),
ok.

View File

@ -25,15 +25,16 @@ connection_low_watermark_field_deprecated.label:
"""deprecated use /license/setting instead""" """deprecated use /license/setting instead"""
key_field.desc: key_field.desc:
"""This configuration parameter is designated for the license key and supports two input formats: """This configuration parameter is designated for the license key and supports below input formats:
- Direct Key: Enter the secret key directly as a string value. - Direct Key: Enter the secret key directly as a string value.
- File Path: Specify the path to a file that contains the secret key. Ensure the path starts with <code>file://</code>. - File Path: Specify the path to a file that contains the secret key. Ensure the path starts with <code>file://</code>.
- "default": Use string value <code>"default"</code> to apply the default trial license.
Note: An invalid license key or an incorrect file path may prevent EMQX from starting successfully. Note: An invalid license key or an incorrect file path may prevent EMQX from starting successfully.
If a file path is used, EMQX attempts to reload the license key every 2 minutes. If a file path is used, EMQX attempts to reload the license key from the file every 2 minutes.
Any failure in reloading the license key will be recorded as an error level log message, Any failure in reloading the license file will be recorded as an error level log message,
without causing system downtime.""" and EMQX continues to apply the license loaded previously."""
key_field.label: key_field.label:
"""License string""" """License string"""