Просмотр исходного кода

Merge pull request #6975 from savonarola/emqx-license-early-check

feature(license): check license before node start
Ilya Averyanov 4 лет назад
Родитель
Сommit
b7aac96282
6 измененных файлов с 68 добавлено и 15 удалено
  1. 41 12
      bin/emqx
  2. 19 0
      bin/nodetool
  3. 1 0
      lib-ee/emqx_license/src/emqx_license.erl
  4. 4 2
      mix.exs
  5. 2 1
      rebar.config.erl
  6. 1 0
      rel/emqx_vars

+ 41 - 12
bin/emqx

@@ -341,19 +341,51 @@ relx_gen_id() {
     od -t x -N 4 /dev/urandom | head -n1 | awk '{print $2}'
 }
 
+call_nodetool() {
+    "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" "$@"
+}
+
 # Control a node
 relx_nodetool() {
     command="$1"; shift
     ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARG" \
-    "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" "$NAME_TYPE" "$NAME" \
+    call_nodetool "$NAME_TYPE" "$NAME" \
                                 -setcookie "$COOKIE" "$command" "$@"
 }
 
 call_hocon() {
-    "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" hocon "$@" \
+    call_nodetool hocon "$@" \
         || die "call_hocon_failed: $*" $?
 }
 
+get_config_value() {
+    path_to_value="$1"
+    call_hocon -s "$SCHEMA_MOD" -I "$CONFIGS_DIR/" -c "$RUNNER_ETC_DIR"/emqx.conf get "$path_to_value" | tr -d \"
+}
+
+check_license() {
+    if [ "$IS_ENTERPRISE" == "no" ]; then
+        return 0
+    fi
+
+    file_license="${EMQX_LICENSE__FILE:-$(get_config_value license.file)}"
+
+    if [[ -n "$file_license" && ("$file_license" != "undefined") ]]; then
+        call_nodetool check_license_file "$file_license"
+    else
+        key_license="${EMQX_LICENSE__KEY:-$(get_config_value license.key)}"
+
+        if [[ -n "$key_license" && ("$key_license" != "undefined") ]]; then
+            call_nodetool check_license_key "$key_license"
+        else
+            echoerr "License not found."
+            echoerr "Please specify one via EMQX_LICENSE__KEY or EMQX_LICENSE__FILE variables"
+            echoerr "or via license.key|file in emqx_enterprise.conf."
+            return 1
+        fi
+    fi
+}
+
 # Run an escript in the node's environment
 relx_escript() {
     shift; scriptpath="$1"; shift
@@ -374,11 +406,6 @@ generate_config() {
     ## changing the config 'log.rotation.size'
     rm -rf "${RUNNER_LOG_DIR}"/*.siz
 
-    EMQX_LICENSE_CONF_OPTION=""
-    if [ "${EMQX_LICENSE_CONF:-}" != "" ]; then
-        EMQX_LICENSE_CONF_OPTION="-c ${EMQX_LICENSE_CONF}"
-    fi
-
     ## timestamp for each generation
     local NOW_TIME
     NOW_TIME="$(call_hocon now_time)"
@@ -387,9 +414,7 @@ generate_config() {
     ## NOTE: the generate command merges environment variables to the base config (emqx.conf),
     ## but does not include the cluster-override.conf and local-override.conf
     ## meaning, certain overrides will not be mapped to app.<time>.config file
-    ## disable SC2086 to allow EMQX_LICENSE_CONF_OPTION to split
-    # shellcheck disable=SC2086
-    call_hocon -v -t "$NOW_TIME" -I "$CONFIGS_DIR/" -s "$SCHEMA_MOD" -c "$RUNNER_ETC_DIR"/emqx.conf $EMQX_LICENSE_CONF_OPTION -d "$RUNNER_DATA_DIR"/configs generate
+    call_hocon -v -t "$NOW_TIME" -I "$CONFIGS_DIR/" -s "$SCHEMA_MOD" -c "$RUNNER_ETC_DIR"/emqx.conf -d "$RUNNER_DATA_DIR"/configs generate
 
     ## filenames are per-hocon convention
     local CONF_FILE="$CONFIGS_DIR/app.$NOW_TIME.config"
@@ -539,7 +564,7 @@ NAME="${EMQX_NODE__NAME:-}"
 if [ -z "$NAME" ]; then
     if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
         # for boot commands, inspect emqx.conf for node name
-        NAME="$(call_hocon -s "$SCHEMA_MOD" -I "$CONFIGS_DIR/" -c "$RUNNER_ETC_DIR"/emqx.conf get node.name | tr -d \")"
+        NAME="$(get_config_value node.name)"
     else
         vm_args_file="$(latest_vm_args 'EMQX_NODE__NAME')"
         NAME="$(grep -E '^-s?name' "${vm_args_file}" | awk '{print $2}')"
@@ -570,7 +595,7 @@ fi
 COOKIE="${EMQX_NODE__COOKIE:-}"
 if [ -z "$COOKIE" ]; then
     if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
-        COOKIE="$(call_hocon -s "$SCHEMA_MOD" -I "$CONFIGS_DIR/" -c "$RUNNER_ETC_DIR"/emqx.conf get node.cookie | tr -d \")"
+        COOKIE="$(get_config_value node.cookie)"
     else
         vm_args_file="$(latest_vm_args 'EMQX_NODE__COOKIE')"
         COOKIE="$(grep -E '^-setcookie' "${vm_args_file}" | awk '{print $2}')"
@@ -742,6 +767,8 @@ case "${COMMAND}" in
         #generate app.config and vm.args
         generate_config "$NAME_TYPE" "$NAME"
 
+        check_license
+
         # Setup beam-required vars
         EMU="beam"
         PROGNAME="${0#*/}"
@@ -790,6 +817,8 @@ case "${COMMAND}" in
         #generate app.config and vm.args
         generate_config "$NAME_TYPE" "$NAME"
 
+        check_license
+
         [ -f "$REL_DIR/$REL_NAME.boot" ] && BOOTFILE="$REL_NAME" || BOOTFILE=start
         FOREGROUNDOPTIONS="-noshell -noinput +Bd"
 

+ 19 - 0
bin/nodetool

@@ -24,6 +24,10 @@ main(Args) ->
         ["hocon" | Rest] ->
             %% forward the call to hocon_cli
             hocon_cli:main(Rest);
+        ["check_license_key", Key] ->
+            check_license(#{key => list_to_binary(Key)});
+        ["check_license_file", File] ->
+            check_license(#{file => list_to_binary(File)});
         _ ->
             do(Args)
     end.
@@ -253,6 +257,21 @@ chkconfig(File) ->
             halt(1)
     end.
 
+check_license(Config) ->
+    ok = application:load(emqx_license),
+    %% This checks formal license validity to ensure
+    %% that the node can successfully start with the given license.
+
+    %% However, a valid license may be expired. In this case, the node will
+    %% start but will not be able to receive connections due to connection limits.
+    %% It may receive license updates from the cluster further.
+    case emqx_license:read_license(Config) of
+        {ok, _} -> ok;
+        {error, Error} ->
+            io:format(standard_error, "Error reading license: ~p~n", [Error]),
+            halt(1)
+    end.
+
 %%
 %% Given a string or binary, parse it into a list of terms, ala file:consult/0
 %%

+ 1 - 0
lib-ee/emqx_license/src/emqx_license.erl

@@ -18,6 +18,7 @@
          check/2,
          unload/0,
          read_license/0,
+         read_license/1,
          update_file/1,
          update_key/1]).
 

+ 4 - 2
mix.exs

@@ -552,7 +552,8 @@ defmodule EMQXUmbrella.MixProject do
       emqx_schema_mod: emqx_schema_mod(edition_type),
       emqx_machine_boot_apps: emqx_machine_boot_app_list(edition_type),
       built_on_arch: built_on(),
-      is_elixir: "yes"
+      is_elixir: "yes",
+      is_enterprise: (if edition_type == :enterprise, do: "yes", else: "no")
     ]
   end
 
@@ -579,7 +580,8 @@ defmodule EMQXUmbrella.MixProject do
       built_on_arch: built_on(),
       emqx_schema_mod: emqx_schema_mod(edition_type),
       emqx_machine_boot_apps: emqx_machine_boot_app_list(edition_type),
-      is_elixir: "yes"
+      is_elixir: "yes",
+      is_enterprise: (if edition_type == :enterprise, do: "yes", else: "no")
     ]
   end
 

+ 2 - 1
rebar.config.erl

@@ -239,10 +239,12 @@ overlay_vars_rel(RelType) ->
 
 overlay_vars_edition(ce) ->
     [ {emqx_schema_mod, emqx_conf_schema}
+    , {is_enterprise, "no"}
     , {emqx_machine_boot_apps, emqx_machine_boot_app_list(ce)}
     ];
 overlay_vars_edition(ee) ->
     [ {emqx_schema_mod, emqx_enterprise_conf_schema}
+    , {is_enterprise, "yes"}
     , {emqx_machine_boot_apps, emqx_machine_boot_app_list(ee)}
     ].
 
@@ -324,7 +326,6 @@ relx_apps(ReleaseType, Edition) ->
     , emqx_plugins
     ]
     ++ [quicer || is_quicer_supported()]
-    %++ [emqx_license || is_enterprise(Edition)]
     ++ [bcrypt || provide_bcrypt_release(ReleaseType)]
     ++ relx_apps_per_rel(ReleaseType)
     ++ relx_additional_apps(ReleaseType, Edition).

+ 1 - 0
rel/emqx_vars

@@ -15,6 +15,7 @@ RUNNER_DATA_DIR="{{ runner_data_dir }}"
 RUNNER_USER="{{ runner_user }}"
 IS_ELIXIR="{{ is_elixir }}"
 SCHEMA_MOD="{{ emqx_schema_mod }}"
+IS_ENTERPRISE="{{ is_enterprise }}"
 
 export EMQX_DESCRIPTION='{{ emqx_description }}'