Explorar o código

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

Zaiming (Stone) Shi %!s(int64=2) %!d(string=hai) anos
pai
achega
14077ec43b

+ 16 - 6
apps/emqx_license/src/emqx_license_parser.erl

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

+ 2 - 2
apps/emqx_license/src/emqx_license_schema.erl

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

+ 6 - 14
apps/emqx_license/test/emqx_license_SUITE.erl

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

+ 2 - 5
apps/emqx_license/test/emqx_license_checker_SUITE.erl

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

+ 2 - 5
apps/emqx_license/test/emqx_license_cli_SUITE.erl

@@ -24,15 +24,12 @@ end_per_suite(_) ->
     ok.
 
 init_per_testcase(_Case, Config) ->
-    ok = persistent_term:put(
-        emqx_license_test_pubkey,
-        emqx_license_test_lib:public_key_pem()
-    ),
+    emqx_license_test_lib:mock_parser(),
     {ok, _} = emqx_cluster_rpc:start_link(node(), emqx_cluster_rpc, 1000),
     Config.
 
 end_per_testcase(_Case, _Config) ->
-    persistent_term:erase(emqx_license_test_pubkey),
+    emqx_license_test_lib:unmock_parser(),
     ok.
 
 set_special_configs(emqx_license) ->

+ 15 - 5
apps/emqx_license/test/emqx_license_http_api_SUITE.erl

@@ -19,6 +19,7 @@ all() ->
     emqx_common_test_helpers:all(?MODULE).
 
 init_per_suite(Config) ->
+    emqx_license_test_lib:mock_parser(),
     _ = application:load(emqx_conf),
     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),
@@ -31,7 +32,7 @@ end_per_suite(_) ->
     emqx_config:put([license], Config),
     RawConfig = #{<<"key">> => LicenseKey},
     emqx_config:put_raw([<<"license">>], RawConfig),
-    persistent_term:erase(emqx_license_test_pubkey),
+    emqx_license_test_lib:unmock_parser(),
     ok.
 
 set_special_configs(emqx_dashboard) ->
@@ -48,10 +49,6 @@ set_special_configs(emqx_license) ->
         <<"connection_high_watermark">> => <<"80%">>
     },
     emqx_config:put_raw([<<"license">>], RawConfig),
-    ok = persistent_term:put(
-        emqx_license_test_pubkey,
-        emqx_license_test_lib:public_key_pem()
-    ),
     ok;
 set_special_configs(_) ->
     ok.
@@ -113,6 +110,19 @@ t_license_info(_Config) ->
     ),
     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) ->
     NewKey = emqx_license_test_lib:make_license(#{max_connections => "999"}),
     Res = request(

+ 10 - 0
apps/emqx_license/test/emqx_license_test_lib.erl

@@ -70,3 +70,13 @@ default_test_license() ->
 
 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.

+ 5 - 4
rel/i18n/emqx_license_schema.hocon

@@ -25,15 +25,16 @@ connection_low_watermark_field_deprecated.label:
 """deprecated use /license/setting instead"""
 
 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.
 - 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.
-If a file path is used, EMQX attempts to reload the license key every 2 minutes.
-Any failure in reloading the license key will be recorded as an error level log message,
-without causing system downtime."""
+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 file will be recorded as an error level log message,
+and EMQX continues to apply the license loaded previously."""
 
 key_field.label:
 """License string"""