Merge remote-tracking branch 'origin/master' into build-with-mix

This commit is contained in:
Thales Macedo Garitezi 2021-11-29 11:28:22 -03:00
commit 2e5596f6ee
No known key found for this signature in database
GPG Key ID: DD279F8152A9B6DD
51 changed files with 1602 additions and 493 deletions

View File

@ -127,7 +127,7 @@ jobs:
matrix: matrix:
profile: # no EDGE for mac profile: # no EDGE for mac
- emqx - emqx
- emqx-ee - emqx-enterprise
otp: otp:
- 24.1.5-2 - 24.1.5-2
macos: macos:
@ -213,7 +213,7 @@ jobs:
profile: ## all editions for linux profile: ## all editions for linux
- emqx-edge - emqx-edge
- emqx - emqx
- emqx-ee - emqx-enterprise
otp: otp:
- 24.1.5-2 # we test with OTP 23, but only build package on OTP 24 versions - 24.1.5-2 # we test with OTP 23, but only build package on OTP 24 versions
arch: arch:
@ -240,9 +240,9 @@ jobs:
- os: raspbian10 - os: raspbian10
profile: emqx profile: emqx
- os: raspbian9 - os: raspbian9
profile: emqx-ee profile: emqx-enterprise
- os: raspbian10 - os: raspbian10
profile: emqx-ee profile: emqx-enterprise
defaults: defaults:
run: run:
@ -333,7 +333,7 @@ jobs:
profile: # all editions for docker profile: # all editions for docker
- emqx-edge - emqx-edge
- emqx - emqx
- emqx-ee - emqx-enterprise
# NOTE: for docker, only support latest otp version, not a matrix # NOTE: for docker, only support latest otp version, not a matrix
otp: otp:
- 24.1.5-2 # update to latest - 24.1.5-2 # update to latest
@ -403,7 +403,7 @@ jobs:
profile: profile:
- emqx-edge - emqx-edge
- emqx - emqx
- emqx-ee - emqx-enterprise
otp: otp:
- 24.1.5-2 - 24.1.5-2
@ -463,7 +463,7 @@ jobs:
- name: update repo.emqx.io - name: update repo.emqx.io
if: github.event_name == 'release' if: github.event_name == 'release'
run: | run: |
if [ "${{ matrix. profile }}" = 'emqx-ee' ]; then if [ "${{ matrix.profile }}" = 'emqx-enterprise' ]; then
BOOL_FLAG_NAME="emqx_ee" BOOL_FLAG_NAME="emqx_ee"
else else
BOOL_FLAG_NAME="emqx_ce" BOOL_FLAG_NAME="emqx_ce"

View File

@ -22,7 +22,7 @@ jobs:
profile: profile:
- emqx-edge - emqx-edge
- emqx - emqx
- emqx-ee - emqx-enterprise
otp: otp:
- 24.1.5-2 - 24.1.5-2
os: os:
@ -53,7 +53,7 @@ jobs:
matrix: matrix:
profile: profile:
- emqx - emqx
- emqx-ee - emqx-enterprise
otp: otp:
- 24.1.5-2 - 24.1.5-2
macos: macos:

View File

@ -27,7 +27,7 @@ jobs:
run: | run: |
echo "https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com" > $HOME/.git-credentials echo "https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com" > $HOME/.git-credentials
git config --global credential.helper store git config --global credential.helper store
make emqx-ee-zip make emqx-enterprise-zip
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
with: with:
name: emqx-broker name: emqx-broker

View File

@ -38,9 +38,9 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
profile: profile:
- emqx-edge
- emqx - emqx
- emqx-ee - emqx-edge
- emqx-enterprise
cluster_db_backend: cluster_db_backend:
- mnesia - mnesia
- rlog - rlog
@ -88,7 +88,7 @@ jobs:
matrix: matrix:
profile: profile:
- emqx - emqx
# - emqx-ee # TODO test enterprise # - emqx-enterprise # TODO test enterprise
steps: steps:
- uses: actions/download-artifact@v2 - uses: actions/download-artifact@v2

View File

@ -17,7 +17,7 @@ jobs:
matrix: matrix:
profile: profile:
- emqx - emqx
- emqx-ee - emqx-enterprise
otp_vsn: otp_vsn:
- 24.1.5-2 - 24.1.5-2
@ -68,7 +68,7 @@ jobs:
if [ $PROFILE = "emqx" ];then if [ $PROFILE = "emqx" ];then
broker="emqx-ce" broker="emqx-ce"
else else
broker="emqx-ee" broker="emqx-enterprise"
fi fi
echo "BROKER=$broker" >> $GITHUB_ENV echo "BROKER=$broker" >> $GITHUB_ENV

View File

@ -23,12 +23,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: set git credentials
run: |
if make emqx-ee --dry-run > /dev/null 2>&1; then
echo "https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com" > $HOME/.git-credentials
git config --global credential.helper store
fi
- name: xref - name: xref
run: make xref run: make xref
- name: dialyzer - name: dialyzer
@ -45,12 +39,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: set git credentials
run: |
if make emqx-ee --dry-run > /dev/null 2>&1; then
echo "https://ci%40emqx.io:${{ secrets.CI_GIT_TOKEN }}@github.com" > $HOME/.git-credentials
git config --global credential.helper store
fi
- name: proper - name: proper
run: make proper run: make proper

View File

@ -15,8 +15,8 @@ ifeq ($(OS),Windows_NT)
endif endif
PROFILE ?= emqx PROFILE ?= emqx
REL_PROFILES := emqx emqx-edge emqx-ee REL_PROFILES := emqx emqx-edge emqx-enterprise
PKG_PROFILES := emqx-pkg emqx-edge-pkg emqx-ee-pkg PKG_PROFILES := emqx-pkg emqx-edge-pkg emqx-enterprise-pkg
PROFILES := $(REL_PROFILES) $(PKG_PROFILES) default PROFILES := $(REL_PROFILES) $(PKG_PROFILES) default
CT_NODE_NAME ?= 'test@127.0.0.1' CT_NODE_NAME ?= 'test@127.0.0.1'
@ -182,7 +182,7 @@ ALL_ZIPS = $(REL_PROFILES)
$(foreach zt,$(ALL_ZIPS),$(eval $(call gen-docker-target,$(zt)))) $(foreach zt,$(ALL_ZIPS),$(eval $(call gen-docker-target,$(zt))))
## emqx-docker-testing ## emqx-docker-testing
## emqx-ee-docker-testing ## emqx-enterprise-docker-testing
## is to directly copy a unzipped zip-package to a ## is to directly copy a unzipped zip-package to a
## base image such as ubuntu20.04. Mostly for testing ## base image such as ubuntu20.04. Mostly for testing
.PHONY: $(REL_PROFILES:%=%-docker-testing) .PHONY: $(REL_PROFILES:%=%-docker-testing)

View File

@ -577,8 +577,9 @@ mqtt {
## @doc mqtt.max_topic_levels ## @doc mqtt.max_topic_levels
## ValueType: Integer ## ValueType: Integer
## Range: [1, 65535] ## Range: [1, 65535]
## Default: 65535 ## Default: 128
max_topic_levels = 65535 ## Depth so big may lead to subscribing performance issues
max_topic_levels = 128
## Maximum QoS allowed. ## Maximum QoS allowed.
## ##

View File

@ -18,6 +18,7 @@
-export([ is_store_enabled/0 -export([ is_store_enabled/0
, init_db_backend/0 , init_db_backend/0
, storage_type/0
]). ]).
-export([ discard/2 -export([ discard/2
@ -82,9 +83,17 @@
init_db_backend() -> init_db_backend() ->
case is_store_enabled() of case is_store_enabled() of
true -> true ->
ok = emqx_trie:create_session_trie(), StorageType = storage_type(),
emqx_persistent_session_mnesia_backend:create_tables(), ok = emqx_trie:create_session_trie(StorageType),
persistent_term:put(?db_backend_key, emqx_persistent_session_mnesia_backend), ok = emqx_session_router:create_router_tab(StorageType),
case StorageType of
disc ->
emqx_persistent_session_mnesia_disc_backend:create_tables(),
persistent_term:put(?db_backend_key, emqx_persistent_session_mnesia_disc_backend);
ram ->
emqx_persistent_session_mnesia_ram_backend:create_tables(),
persistent_term:put(?db_backend_key, emqx_persistent_session_mnesia_ram_backend)
end,
ok; ok;
false -> false ->
persistent_term:put(?db_backend_key, emqx_persistent_session_dummy_backend), persistent_term:put(?db_backend_key, emqx_persistent_session_dummy_backend),
@ -94,6 +103,9 @@ init_db_backend() ->
is_store_enabled() -> is_store_enabled() ->
emqx_config:get(?is_enabled_key). emqx_config:get(?is_enabled_key).
storage_type() ->
emqx_config:get(?storage_type_key).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Session message ADT API %% Session message ADT API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -14,9 +14,14 @@
%% limitations under the License. %% limitations under the License.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-define(SESSION_STORE, emqx_session_store). -define(SESSION_STORE_DISC, emqx_session_store_disc).
-define(SESS_MSG_TAB, emqx_session_msg). -define(SESSION_STORE_RAM, emqx_session_store_ram).
-define(MSG_TAB, emqx_persistent_msg).
-define(SESS_MSG_TAB_DISC, emqx_session_msg_disc).
-define(SESS_MSG_TAB_RAM, emqx_session_msg_ram).
-define(MSG_TAB_DISC, emqx_persistent_msg_disc).
-define(MSG_TAB_RAM, emqx_persistent_msg_ram).
-record(session_store, { client_id :: binary() -record(session_store, { client_id :: binary()
, expiry_interval :: non_neg_integer() , expiry_interval :: non_neg_integer()
@ -28,6 +33,7 @@
-define(db_backend_key, [persistent_session_store, db_backend]). -define(db_backend_key, [persistent_session_store, db_backend]).
-define(is_enabled_key, [persistent_session_store, enabled]). -define(is_enabled_key, [persistent_session_store, enabled]).
-define(storage_type_key, [persistent_session_store, storage_type]).
-define(msg_retain, [persistent_session_store, max_retain_undelivered]). -define(msg_retain, [persistent_session_store, max_retain_undelivered]).
-define(db_backend, (persistent_term:get(?db_backend_key))). -define(db_backend, (persistent_term:get(?db_backend_key))).

View File

@ -14,7 +14,7 @@
%% limitations under the License. %% limitations under the License.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-module(emqx_persistent_session_mnesia_backend). -module(emqx_persistent_session_mnesia_disc_backend).
-include("emqx.hrl"). -include("emqx.hrl").
-include("emqx_persistent_session.hrl"). -include("emqx_persistent_session.hrl").
@ -36,7 +36,7 @@
]). ]).
create_tables() -> create_tables() ->
ok = mria:create_table(?SESSION_STORE, [ ok = mria:create_table(?SESSION_STORE_DISC, [
{type, set}, {type, set},
{rlog_shard, ?PERSISTENT_SESSION_SHARD}, {rlog_shard, ?PERSISTENT_SESSION_SHARD},
{storage, disc_copies}, {storage, disc_copies},
@ -44,7 +44,7 @@ create_tables() ->
{attributes, record_info(fields, session_store)}, {attributes, record_info(fields, session_store)},
{storage_properties, [{ets, [{read_concurrency, true}]}]}]), {storage_properties, [{ets, [{read_concurrency, true}]}]}]),
ok = mria:create_table(?SESS_MSG_TAB, [ ok = mria:create_table(?SESS_MSG_TAB_DISC, [
{type, ordered_set}, {type, ordered_set},
{rlog_shard, ?PERSISTENT_SESSION_SHARD}, {rlog_shard, ?PERSISTENT_SESSION_SHARD},
{storage, disc_copies}, {storage, disc_copies},
@ -53,7 +53,7 @@ create_tables() ->
{storage_properties, [{ets, [{read_concurrency, true}, {storage_properties, [{ets, [{read_concurrency, true},
{write_concurrency, true}]}]}]), {write_concurrency, true}]}]}]),
ok = mria:create_table(?MSG_TAB, [ ok = mria:create_table(?MSG_TAB_DISC, [
{type, ordered_set}, {type, ordered_set},
{rlog_shard, ?PERSISTENT_SESSION_SHARD}, {rlog_shard, ?PERSISTENT_SESSION_SHARD},
{storage, disc_copies}, {storage, disc_copies},
@ -63,43 +63,43 @@ create_tables() ->
{write_concurrency, true}]}]}]). {write_concurrency, true}]}]}]).
first_session_message() -> first_session_message() ->
mnesia:dirty_first(?SESS_MSG_TAB). mnesia:dirty_first(?SESS_MSG_TAB_DISC).
next_session_message(Key) -> next_session_message(Key) ->
mnesia:dirty_next(?SESS_MSG_TAB, Key). mnesia:dirty_next(?SESS_MSG_TAB_DISC, Key).
first_message_id() -> first_message_id() ->
mnesia:dirty_first(?MSG_TAB). mnesia:dirty_first(?MSG_TAB_DISC).
next_message_id(Key) -> next_message_id(Key) ->
mnesia:dirty_next(?MSG_TAB, Key). mnesia:dirty_next(?MSG_TAB_DISC, Key).
delete_message(Key) -> delete_message(Key) ->
mria:dirty_delete(?MSG_TAB, Key). mria:dirty_delete(?MSG_TAB_DISC, Key).
delete_session_message(Key) -> delete_session_message(Key) ->
mria:dirty_delete(?SESS_MSG_TAB, Key). mria:dirty_delete(?SESS_MSG_TAB_DISC, Key).
put_session_store(SS) -> put_session_store(SS) ->
mria:dirty_write(?SESSION_STORE, SS). mria:dirty_write(?SESSION_STORE_DISC, SS).
delete_session_store(ClientID) -> delete_session_store(ClientID) ->
mria:dirty_delete(?SESSION_STORE, ClientID). mria:dirty_delete(?SESSION_STORE_DISC, ClientID).
lookup_session_store(ClientID) -> lookup_session_store(ClientID) ->
case mnesia:dirty_read(?SESSION_STORE, ClientID) of case mnesia:dirty_read(?SESSION_STORE_DISC, ClientID) of
[] -> none; [] -> none;
[SS] -> {value, SS} [SS] -> {value, SS}
end. end.
put_session_message(SessMsg) -> put_session_message(SessMsg) ->
mria:dirty_write(?SESS_MSG_TAB, SessMsg). mria:dirty_write(?SESS_MSG_TAB_DISC, SessMsg).
put_message(Msg) -> put_message(Msg) ->
mria:dirty_write(?MSG_TAB, Msg). mria:dirty_write(?MSG_TAB_DISC, Msg).
get_message(MsgId) -> get_message(MsgId) ->
case mnesia:read(?MSG_TAB, MsgId) of case mnesia:read(?MSG_TAB_DISC, MsgId) of
[] -> error({msg_not_found, MsgId}); [] -> error({msg_not_found, MsgId});
[Msg] -> Msg [Msg] -> Msg
end. end.

View File

@ -0,0 +1,110 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_persistent_session_mnesia_ram_backend).
-include("emqx.hrl").
-include("emqx_persistent_session.hrl").
-export([ create_tables/0
, first_message_id/0
, next_message_id/1
, delete_message/1
, first_session_message/0
, next_session_message/1
, delete_session_message/1
, put_session_store/1
, delete_session_store/1
, lookup_session_store/1
, put_session_message/1
, put_message/1
, get_message/1
, ro_transaction/1
]).
create_tables() ->
ok = mria:create_table(?SESSION_STORE_RAM, [
{type, set},
{rlog_shard, ?PERSISTENT_SESSION_SHARD},
{storage, ram_copies},
{record_name, session_store},
{attributes, record_info(fields, session_store)},
{storage_properties, [{ets, [{read_concurrency, true}]}]}]),
ok = mria:create_table(?SESS_MSG_TAB_RAM, [
{type, ordered_set},
{rlog_shard, ?PERSISTENT_SESSION_SHARD},
{storage, ram_copies},
{record_name, session_msg},
{attributes, record_info(fields, session_msg)},
{storage_properties, [{ets, [{read_concurrency, true},
{write_concurrency, true}]}]}]),
ok = mria:create_table(?MSG_TAB_RAM, [
{type, ordered_set},
{rlog_shard, ?PERSISTENT_SESSION_SHARD},
{storage, ram_copies},
{record_name, message},
{attributes, record_info(fields, message)},
{storage_properties, [{ets, [{read_concurrency, true},
{write_concurrency, true}]}]}]).
first_session_message() ->
mnesia:dirty_first(?SESS_MSG_TAB_RAM).
next_session_message(Key) ->
mnesia:dirty_next(?SESS_MSG_TAB_RAM, Key).
first_message_id() ->
mnesia:dirty_first(?MSG_TAB_RAM).
next_message_id(Key) ->
mnesia:dirty_next(?MSG_TAB_RAM, Key).
delete_message(Key) ->
mria:dirty_delete(?MSG_TAB_RAM, Key).
delete_session_message(Key) ->
mria:dirty_delete(?SESS_MSG_TAB_RAM, Key).
put_session_store(SS) ->
mria:dirty_write(?SESSION_STORE_RAM, SS).
delete_session_store(ClientID) ->
mria:dirty_delete(?SESSION_STORE_RAM, ClientID).
lookup_session_store(ClientID) ->
case mnesia:dirty_read(?SESSION_STORE_RAM, ClientID) of
[] -> none;
[SS] -> {value, SS}
end.
put_session_message(SessMsg) ->
mria:dirty_write(?SESS_MSG_TAB_RAM, SessMsg).
put_message(Msg) ->
mria:dirty_write(?MSG_TAB_RAM, Msg).
get_message(MsgId) ->
case mnesia:read(?MSG_TAB_RAM, MsgId) of
[] -> error({msg_not_found, MsgId});
[Msg] -> Msg
end.
ro_transaction(Fun) ->
{atomic, Res} = mria:ro_transaction(?PERSISTENT_SESSION_SHARD, Fun),
Res.

View File

@ -20,8 +20,10 @@
-export([ delete_direct_route/2 -export([ delete_direct_route/2
, delete_trie_route/2 , delete_trie_route/2
, delete_session_trie_route/2
, insert_direct_route/2 , insert_direct_route/2
, insert_trie_route/2 , insert_trie_route/2
, insert_session_trie_route/2
, maybe_trans/3 , maybe_trans/3
]). ]).
@ -30,8 +32,14 @@ insert_direct_route(Tab, Route) ->
insert_trie_route(RouteTab, Route = #route{topic = Topic}) -> insert_trie_route(RouteTab, Route = #route{topic = Topic}) ->
case mnesia:wread({RouteTab, Topic}) of case mnesia:wread({RouteTab, Topic}) of
[] when RouteTab =:= emqx_route -> emqx_trie:insert(Topic); [] -> emqx_trie:insert(Topic);
[] when RouteTab =:= emqx_session_route -> emqx_trie:insert_session(Topic); _ -> ok
end,
mnesia:write(RouteTab, Route, sticky_write).
insert_session_trie_route(RouteTab, Route = #route{topic = Topic}) ->
case mnesia:wread({RouteTab, Topic}) of
[] -> emqx_trie:insert_session(Topic);
_ -> ok _ -> ok
end, end,
mnesia:write(RouteTab, Route, sticky_write). mnesia:write(RouteTab, Route, sticky_write).
@ -39,14 +47,20 @@ insert_trie_route(RouteTab, Route = #route{topic = Topic}) ->
delete_direct_route(RouteTab, Route) -> delete_direct_route(RouteTab, Route) ->
mria:dirty_delete_object(RouteTab, Route). mria:dirty_delete_object(RouteTab, Route).
delete_trie_route(RouteTab, Route = #route{topic = Topic}) -> delete_trie_route(RouteTab, Route) ->
delete_trie_route(RouteTab, Route, normal).
delete_session_trie_route(RouteTab, Route) ->
delete_trie_route(RouteTab, Route, session).
delete_trie_route(RouteTab, Route = #route{topic = Topic}, Type) ->
case mnesia:wread({RouteTab, Topic}) of case mnesia:wread({RouteTab, Topic}) of
[R] when R =:= Route -> [R] when R =:= Route ->
%% Remove route and trie %% Remove route and trie
ok = mnesia:delete_object(RouteTab, Route, sticky_write), ok = mnesia:delete_object(RouteTab, Route, sticky_write),
case RouteTab of case Type of
emqx_route -> emqx_trie:delete(Topic); normal -> emqx_trie:delete(Topic);
emqx_session_route -> emqx_trie:delete_session(Topic) session -> emqx_trie:delete_session(Topic)
end; end;
[_|_] -> [_|_] ->
%% Remove route only %% Remove route only

View File

@ -161,19 +161,50 @@ roots(low) ->
fields("persistent_session_store") -> fields("persistent_session_store") ->
[ {"enabled", [ {"enabled",
sc(boolean(), sc(boolean(),
#{ default => "false" #{ default => false
, description => """
Use the database to store information about persistent sessions.
This makes it possible to migrate a client connection to another
cluster node if a node is stopped.
"""
})},
{"storage_type",
sc(hoconsc:union([ram, disc]),
#{ default => disc
, description => """
Store information about persistent sessions on disc or in ram.
If ram is chosen, all information about persistent sessions remains
as long as at least one node in a cluster is alive to keep the information.
If disc is chosen, the information is persisted on disc and will survive
cluster restart, at the price of more disc usage and less throughput.
"""
})}, })},
{"max_retain_undelivered", {"max_retain_undelivered",
sc(duration(), sc(duration(),
#{ default => "1h" #{ default => "1h"
, description => """
The time messages that was not delivered to a persistent session
is stored before being garbage collected if the node the previous
session was handled on restarts of is stopped.
"""
})}, })},
{"message_gc_interval", {"message_gc_interval",
sc(duration(), sc(duration(),
#{ default => "1h" #{ default => "1h"
, description => """
The starting interval for garbage collection of undelivered messages to
a persistent session. This affects how often the \"max_retain_undelivered\"
is checked for removal.
"""
})}, })},
{"session_message_gc_interval", {"session_message_gc_interval",
sc(duration(), sc(duration(),
#{ default => "1m" #{ default => "1m"
, description => """
The starting interval for garbage collection of transient data for
persistent session messages. This does not affect the life time length
of persistent session messages.
"""
})} })}
]; ];

View File

@ -24,12 +24,8 @@
-include_lib("snabbkaffe/include/snabbkaffe.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl").
%% Mnesia bootstrap
-export([mnesia/1]).
-boot_mnesia({mnesia, [boot]}).
-export([ create_init_tab/0 -export([ create_init_tab/0
, create_router_tab/1
, start_link/2]). , start_link/2]).
%% Route APIs %% Route APIs
@ -60,7 +56,8 @@
-type(dest() :: node() | {group(), node()}). -type(dest() :: node() | {group(), node()}).
-define(ROUTE_TAB, emqx_session_route). -define(ROUTE_RAM_TAB, emqx_session_route_ram).
-define(ROUTE_DISC_TAB, emqx_session_route_disc).
-define(SESSION_INIT_TAB, session_init_tab). -define(SESSION_INIT_TAB, session_init_tab).
@ -68,13 +65,22 @@
%% Mnesia bootstrap %% Mnesia bootstrap
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
mnesia(boot) -> create_router_tab(disc) ->
ok = mria:create_table(?ROUTE_TAB, [ ok = mria:create_table(?ROUTE_DISC_TAB, [
{type, bag}, {type, bag},
{rlog_shard, ?ROUTE_SHARD}, {rlog_shard, ?ROUTE_SHARD},
{storage, disc_copies}, {storage, disc_copies},
{record_name, route}, {record_name, route},
{attributes, record_info(fields, route)}, {attributes, record_info(fields, route)},
{storage_properties, [{ets, [{read_concurrency, true},
{write_concurrency, true}]}]}]);
create_router_tab(ram) ->
ok = mria:create_table(?ROUTE_RAM_TAB, [
{type, bag},
{rlog_shard, ?ROUTE_SHARD},
{storage, ram_copies},
{record_name, route},
{attributes, record_info(fields, route)},
{storage_properties, [{ets, [{read_concurrency, true}, {storage_properties, [{ets, [{read_concurrency, true},
{write_concurrency, true}]}]}]). {write_concurrency, true}]}]}]).
@ -103,11 +109,11 @@ do_add_route(Topic, SessionID) when is_binary(Topic) ->
false -> false ->
case emqx_topic:wildcard(Topic) of case emqx_topic:wildcard(Topic) of
true -> true ->
Fun = fun emqx_router_utils:insert_trie_route/2, Fun = fun emqx_router_utils:insert_session_trie_route/2,
emqx_router_utils:maybe_trans(Fun, [?ROUTE_TAB, Route], emqx_router_utils:maybe_trans(Fun, [route_tab(), Route],
?PERSISTENT_SESSION_SHARD); ?PERSISTENT_SESSION_SHARD);
false -> false ->
emqx_router_utils:insert_direct_route(?ROUTE_TAB, Route) emqx_router_utils:insert_direct_route(route_tab(), Route)
end end
end. end.
@ -136,10 +142,10 @@ do_delete_route(Topic, SessionID) ->
Route = #route{topic = Topic, dest = SessionID}, Route = #route{topic = Topic, dest = SessionID},
case emqx_topic:wildcard(Topic) of case emqx_topic:wildcard(Topic) of
true -> true ->
Fun = fun emqx_router_utils:delete_trie_route/2, Fun = fun emqx_router_utils:delete_session_trie_route/2,
emqx_router_utils:maybe_trans(Fun, [?ROUTE_TAB, Route], ?PERSISTENT_SESSION_SHARD); emqx_router_utils:maybe_trans(Fun, [route_tab(), Route], ?PERSISTENT_SESSION_SHARD);
false -> false ->
emqx_router_utils:delete_direct_route(?ROUTE_TAB, Route) emqx_router_utils:delete_direct_route(route_tab(), Route)
end. end.
%% @doc Print routes to a topic %% @doc Print routes to a topic
@ -273,4 +279,10 @@ init_resume_worker(RemotePid, SessionID, #{ pmon := Pmon } = State) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
lookup_routes(Topic) -> lookup_routes(Topic) ->
ets:lookup(?ROUTE_TAB, Topic). ets:lookup(route_tab(), Topic).
route_tab() ->
case emqx_persistent_session:storage_type() of
disc -> ?ROUTE_DISC_TAB;
ram -> ?ROUTE_RAM_TAB
end.

View File

@ -187,11 +187,20 @@ suppress(Key, SuccFun, State = #{events := Events}) ->
procinfo(Pid) -> procinfo(Pid) ->
[{pid, Pid} | procinfo_l(emqx_vm:get_process_gc_info(Pid))] ++ [{pid, Pid} | procinfo_l(emqx_vm:get_process_gc_info(Pid))] ++
get_proc_lib_initial_call(Pid) ++
procinfo_l(emqx_vm:get_process_info(Pid)). procinfo_l(emqx_vm:get_process_info(Pid)).
procinfo_l(undefined) -> []; procinfo_l(undefined) -> [];
procinfo_l(List) -> List. procinfo_l(List) -> List.
get_proc_lib_initial_call(Pid) ->
case proc_lib:initial_call(Pid) of
false ->
[];
InitialCall ->
[{proc_lib_initial_call, InitialCall}]
end.
portinfo(Port) -> portinfo(Port) ->
[{port, Port} | erlang:port_info(Port)]. [{port, Port} | erlang:port_info(Port)].

View File

@ -20,7 +20,7 @@
%% Mnesia bootstrap %% Mnesia bootstrap
-export([ mnesia/1 -export([ mnesia/1
, create_session_trie/0 , create_session_trie/1
]). ]).
-boot_mnesia({mnesia, [boot]}). -boot_mnesia({mnesia, [boot]}).
@ -48,7 +48,8 @@
-endif. -endif.
-define(TRIE, emqx_trie). -define(TRIE, emqx_trie).
-define(SESSION_TRIE, emqx_session_trie). -define(SESSION_DISC_TRIE, emqx_session_trie_disc).
-define(SESSION_RAM_TRIE, emqx_session_trie_ram).
-define(PREFIX(Prefix), {Prefix, 0}). -define(PREFIX(Prefix), {Prefix, 0}).
-define(TOPIC(Topic), {Topic, 1}). -define(TOPIC(Topic), {Topic, 1}).
@ -75,16 +76,27 @@ mnesia(boot) ->
{type, ordered_set}, {type, ordered_set},
{storage_properties, StoreProps}]). {storage_properties, StoreProps}]).
create_session_trie() -> create_session_trie(disc) ->
StoreProps = [{ets, [{read_concurrency, true}, StoreProps = [{ets, [{read_concurrency, true},
{write_concurrency, true} {write_concurrency, true}
]}], ]}],
ok = mria:create_table(?SESSION_TRIE, ok = mria:create_table(?SESSION_DISC_TRIE,
[{rlog_shard, ?ROUTE_SHARD}, [{rlog_shard, ?ROUTE_SHARD},
{storage, disc_copies}, {storage, disc_copies},
{record_name, ?TRIE}, {record_name, ?TRIE},
{attributes, record_info(fields, ?TRIE)}, {attributes, record_info(fields, ?TRIE)},
{type, ordered_set}, {type, ordered_set},
{storage_properties, StoreProps}]);
create_session_trie(ram) ->
StoreProps = [{ets, [{read_concurrency, true},
{write_concurrency, true}
]}],
ok = mria:create_table(?SESSION_RAM_TRIE,
[{rlog_shard, ?ROUTE_SHARD},
{storage, ram_copies},
{record_name, ?TRIE},
{attributes, record_info(fields, ?TRIE)},
{type, ordered_set},
{storage_properties, StoreProps}]). {storage_properties, StoreProps}]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -98,7 +110,7 @@ insert(Topic) when is_binary(Topic) ->
-spec(insert_session(emqx_topic:topic()) -> ok). -spec(insert_session(emqx_topic:topic()) -> ok).
insert_session(Topic) when is_binary(Topic) -> insert_session(Topic) when is_binary(Topic) ->
insert(Topic, ?SESSION_TRIE). insert(Topic, session_trie()).
insert(Topic, Trie) when is_binary(Topic) -> insert(Topic, Trie) when is_binary(Topic) ->
{TopicKey, PrefixKeys} = make_keys(Topic), {TopicKey, PrefixKeys} = make_keys(Topic),
@ -115,7 +127,7 @@ delete(Topic) when is_binary(Topic) ->
%% @doc Delete a topic filter from the trie. %% @doc Delete a topic filter from the trie.
-spec(delete_session(emqx_topic:topic()) -> ok). -spec(delete_session(emqx_topic:topic()) -> ok).
delete_session(Topic) when is_binary(Topic) -> delete_session(Topic) when is_binary(Topic) ->
delete(Topic, ?SESSION_TRIE). delete(Topic, session_trie()).
delete(Topic, Trie) when is_binary(Topic) -> delete(Topic, Trie) when is_binary(Topic) ->
{TopicKey, PrefixKeys} = make_keys(Topic), {TopicKey, PrefixKeys} = make_keys(Topic),
@ -131,7 +143,7 @@ match(Topic) when is_binary(Topic) ->
-spec(match_session(emqx_topic:topic()) -> list(emqx_topic:topic())). -spec(match_session(emqx_topic:topic()) -> list(emqx_topic:topic())).
match_session(Topic) when is_binary(Topic) -> match_session(Topic) when is_binary(Topic) ->
match(Topic, ?SESSION_TRIE). match(Topic, session_trie()).
match(Topic, Trie) when is_binary(Topic) -> match(Topic, Trie) when is_binary(Topic) ->
Words = emqx_topic:words(Topic), Words = emqx_topic:words(Topic),
@ -154,7 +166,7 @@ match(Topic, Trie) when is_binary(Topic) ->
empty() -> empty(?TRIE). empty() -> empty(?TRIE).
empty_session() -> empty_session() ->
empty(?SESSION_TRIE). empty(session_trie()).
empty(Trie) -> ets:first(Trie) =:= '$end_of_table'. empty(Trie) -> ets:first(Trie) =:= '$end_of_table'.
@ -164,12 +176,19 @@ lock_tables() ->
-spec lock_session_tables() -> ok. -spec lock_session_tables() -> ok.
lock_session_tables() -> lock_session_tables() ->
mnesia:write_lock_table(?SESSION_TRIE). mnesia:write_lock_table(session_trie()).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
session_trie() ->
case emqx_persistent_session:storage_type() of
disc -> ?SESSION_DISC_TRIE;
ram -> ?SESSION_RAM_TRIE
end.
make_keys(Topic) -> make_keys(Topic) ->
Words = emqx_topic:words(Topic), Words = emqx_topic:words(Topic),
{?TOPIC(Topic), [?PREFIX(Prefix) || Prefix <- make_prefixes(Words)]}. {?TOPIC(Topic), [?PREFIX(Prefix) || Prefix <- make_prefixes(Words)]}.

View File

@ -61,7 +61,7 @@
]). ]).
-define(PROCESS_INFO_KEYS, [initial_call, -define(PROCESS_INFO_KEYS, [initial_call,
current_function, current_stacktrace,
registered_name, registered_name,
status, status,
message_queue_len, message_queue_len,

View File

@ -59,7 +59,7 @@ mqtt_conf() ->
max_inflight => 32,max_mqueue_len => 1000, max_inflight => 32,max_mqueue_len => 1000,
max_packet_size => 1048576,max_qos_allowed => 2, max_packet_size => 1048576,max_qos_allowed => 2,
max_subscriptions => infinity,max_topic_alias => 65535, max_subscriptions => infinity,max_topic_alias => 65535,
max_topic_levels => 65535,mqueue_default_priority => lowest, max_topic_levels => 128,mqueue_default_priority => lowest,
mqueue_priorities => disabled,mqueue_store_qos0 => true, mqueue_priorities => disabled,mqueue_store_qos0 => true,
peer_cert_as_clientid => disabled, peer_cert_as_clientid => disabled,
peer_cert_as_username => disabled, peer_cert_as_username => disabled,
@ -200,7 +200,7 @@ t_chan_caps(_) ->
#{max_clientid_len := 65535, #{max_clientid_len := 65535,
max_qos_allowed := 2, max_qos_allowed := 2,
max_topic_alias := 65535, max_topic_alias := 65535,
max_topic_levels := 65535, max_topic_levels := 128,
retain_available := true, retain_available := true,
shared_subscription := true, shared_subscription := true,
subscription_identifiers := true, subscription_identifiers := true,

View File

@ -50,13 +50,19 @@ groups() ->
SnabbkaffeTCs = [TC || TC <- TCs, is_snabbkaffe_tc(TC)], SnabbkaffeTCs = [TC || TC <- TCs, is_snabbkaffe_tc(TC)],
GCTests = [TC || TC <- TCs, is_gc_tc(TC)], GCTests = [TC || TC <- TCs, is_gc_tc(TC)],
OtherTCs = (TCs -- SnabbkaffeTCs) -- GCTests, OtherTCs = (TCs -- SnabbkaffeTCs) -- GCTests,
[ {persistent_store_enabled, [ {group, no_kill_connection_process} [ {persistent_store_enabled, [ {group, ram_tables}
, {group, kill_connection_process} , {group, disc_tables}
, {group, snabbkaffe}
, {group, gc_tests}
]} ]}
, {persistent_store_disabled, [ {group, no_kill_connection_process} , {persistent_store_disabled, [ {group, no_kill_connection_process}
]} ]}
, { ram_tables, [], [ {group, no_kill_connection_process}
, {group, kill_connection_process}
, {group, snabbkaffe}
, {group, gc_tests}]}
, { disc_tables, [], [ {group, no_kill_connection_process}
, {group, kill_connection_process}
, {group, snabbkaffe}
, {group, gc_tests}]}
, {no_kill_connection_process, [], [{group, tcp}, {group, quic}, {group, ws}]} , {no_kill_connection_process, [], [{group, tcp}, {group, quic}, {group, ws}]}
, { kill_connection_process, [], [{group, tcp}, {group, quic}, {group, ws}]} , { kill_connection_process, [], [{group, tcp}, {group, quic}, {group, ws}]}
, {snabbkaffe, [], [{group, tcp_snabbkaffe}, {group, quic_snabbkaffe}, {group, ws_snabbkaffe}]} , {snabbkaffe, [], [{group, tcp_snabbkaffe}, {group, quic_snabbkaffe}, {group, ws_snabbkaffe}]}
@ -76,15 +82,23 @@ is_gc_tc(TC) ->
re:run(atom_to_list(TC), "^t_gc_") /= nomatch. re:run(atom_to_list(TC), "^t_gc_") /= nomatch.
init_per_group(persistent_store_enabled, Config) -> init_per_group(persistent_store_enabled, Config) ->
[{persistent_store_enabled, true}|Config];
init_per_group(Group, Config) when Group =:= ram_tables; Group =:= disc_tables ->
%% Start Apps %% Start Apps
Reply = case Group =:= ram_tables of
true -> ram;
false -> disc
end,
emqx_common_test_helpers:boot_modules(all), emqx_common_test_helpers:boot_modules(all),
meck:new(emqx_config, [non_strict, passthrough, no_history, no_link]), meck:new(emqx_config, [non_strict, passthrough, no_history, no_link]),
meck:expect(emqx_config, get, fun(?is_enabled_key) -> true; meck:expect(emqx_config, get, fun(?storage_type_key) -> Reply;
(Other) -> meck:passthrough([Other]) (?is_enabled_key) -> true;
(Other) -> meck:passthrough([Other])
end), end),
emqx_common_test_helpers:start_apps([], fun set_special_confs/1), emqx_common_test_helpers:start_apps([], fun set_special_confs/1),
?assertEqual(true, emqx_persistent_session:is_store_enabled()), ?assertEqual(true, emqx_persistent_session:is_store_enabled()),
[{persistent_store_enabled, true}|Config]; ?assertEqual(Reply, emqx_persistent_session:storage_type()),
Config;
init_per_group(persistent_store_disabled, Config) -> init_per_group(persistent_store_disabled, Config) ->
%% Start Apps %% Start Apps
emqx_common_test_helpers:boot_modules(all), emqx_common_test_helpers:boot_modules(all),
@ -125,15 +139,20 @@ init_per_group(gc_tests, Config) ->
receive stop -> ok end receive stop -> ok end
end), end),
meck:new(mnesia, [non_strict, passthrough, no_history, no_link]), meck:new(mnesia, [non_strict, passthrough, no_history, no_link]),
meck:expect(mnesia, dirty_first, fun(?SESS_MSG_TAB) -> ets:first(SessionMsgEts); meck:expect(mnesia, dirty_first, fun(?SESS_MSG_TAB_RAM) -> ets:first(SessionMsgEts);
(?MSG_TAB) -> ets:first(MsgEts); (?SESS_MSG_TAB_DISC) -> ets:first(SessionMsgEts);
(X) -> meck:passthrough(X) (?MSG_TAB_RAM) -> ets:first(MsgEts);
(?MSG_TAB_DISC) -> ets:first(MsgEts);
(X) -> meck:passthrough([X])
end), end),
meck:expect(mnesia, dirty_next, fun(?SESS_MSG_TAB, X) -> ets:next(SessionMsgEts, X); meck:expect(mnesia, dirty_next, fun(?SESS_MSG_TAB_RAM, X) -> ets:next(SessionMsgEts, X);
(?MSG_TAB, X) -> ets:next(MsgEts, X); (?SESS_MSG_TAB_DISC, X) -> ets:next(SessionMsgEts, X);
(?MSG_TAB_RAM, X) -> ets:next(MsgEts, X);
(?MSG_TAB_DISC, X) -> ets:next(MsgEts, X);
(Tab, X) -> meck:passthrough([Tab, X]) (Tab, X) -> meck:passthrough([Tab, X])
end), end),
meck:expect(mnesia, dirty_delete, fun(?MSG_TAB, X) -> ets:delete(MsgEts, X); meck:expect(mnesia, dirty_delete, fun(?MSG_TAB_RAM, X) -> ets:delete(MsgEts, X);
(?MSG_TAB_DISC, X) -> ets:delete(MsgEts, X);
(Tab, X) -> meck:passthrough([Tab, X]) (Tab, X) -> meck:passthrough([Tab, X])
end), end),
[{store_owner, Pid}, {session_msg_store, SessionMsgEts}, {msg_store, MsgEts} | Config]. [{store_owner, Pid}, {session_msg_store, SessionMsgEts}, {msg_store, MsgEts} | Config].
@ -154,7 +173,7 @@ end_per_group(gc_tests, Config) ->
meck:unload(mnesia), meck:unload(mnesia),
?config(store_owner, Config) ! stop, ?config(store_owner, Config) ! stop,
ok; ok;
end_per_group(persistent_store_enabled, _Config) -> end_per_group(Group, _Config) when Group =:= ram_tables; Group =:= disc_tables ->
meck:unload(emqx_config), meck:unload(emqx_config),
emqx_common_test_helpers:stop_apps([]); emqx_common_test_helpers:stop_apps([]);
end_per_group(persistent_store_disabled, _Config) -> end_per_group(persistent_store_disabled, _Config) ->
@ -250,7 +269,7 @@ maybe_kill_connection_process(ClientId, Config) ->
end. end.
wait_for_cm_unregister(ClientId) -> wait_for_cm_unregister(ClientId) ->
wait_for_cm_unregister(ClientId, 10). wait_for_cm_unregister(ClientId, 100).
wait_for_cm_unregister(_ClientId, 0) -> wait_for_cm_unregister(_ClientId, 0) ->
error(cm_did_not_unregister); error(cm_did_not_unregister);
@ -884,12 +903,16 @@ t_snabbkaffe_buffered_messages(Config) ->
%% Make the resume init phase wait until the first message is delivered. %% Make the resume init phase wait until the first message is delivered.
?force_ordering( #{ ?snk_kind := ps_worker_deliver }, ?force_ordering( #{ ?snk_kind := ps_worker_deliver },
#{ ?snk_kind := ps_resume_end }), #{ ?snk_kind := ps_resume_end }),
Parent = self(),
spawn_link(fun() -> spawn_link(fun() ->
?block_until(#{?snk_kind := ps_marker_pendings_msgs}, infinity, 5000), ?block_until(#{?snk_kind := ps_marker_pendings_msgs}, infinity, 5000),
publish(Topic, Payloads2, true) publish(Topic, Payloads2, true),
Parent ! publish_done,
ok
end), end),
{ok, Client2} = emqtt:start_link([{clean_start, false} | EmqttOpts]), {ok, Client2} = emqtt:start_link([{clean_start, false} | EmqttOpts]),
{ok, _} = emqtt:ConnFun(Client2), {ok, _} = emqtt:ConnFun(Client2),
receive publish_done -> ok after 10000 -> error(too_long_to_publish) end,
Msgs = receive_messages(length(Payloads1) + length(Payloads2) + 1), Msgs = receive_messages(length(Payloads1) + length(Payloads2) + 1),
ReceivedPayloads = [P || #{ payload := P } <- Msgs], ReceivedPayloads = [P || #{ payload := P } <- Msgs],
?assertEqual(lists:sort(Payloads1 ++ Payloads2), ?assertEqual(lists:sort(Payloads1 ++ Payloads2),

View File

@ -71,23 +71,48 @@ init_per_testcase(t_sys_mon2, Config) ->
(_) -> ok (_) -> ok
end), end),
Config; Config;
init_per_testcase(t_procinfo, Config) ->
emqx_common_test_helpers:boot_modules(all),
emqx_common_test_helpers:start_apps([]),
ok = meck:new(emqx_vm, [passthrough, no_history]),
Config;
init_per_testcase(_, Config) -> init_per_testcase(_, Config) ->
emqx_common_test_helpers:boot_modules(all), emqx_common_test_helpers:boot_modules(all),
emqx_common_test_helpers:start_apps([]), emqx_common_test_helpers:start_apps([]),
Config. Config.
end_per_testcase(t_procinfo, _Config) ->
ok = meck:unload(emqx_vm),
emqx_common_test_helpers:stop_apps([]);
end_per_testcase(_, _Config) -> end_per_testcase(_, _Config) ->
emqx_common_test_helpers:stop_apps([]). emqx_common_test_helpers:stop_apps([]).
t_procinfo(_) -> t_procinfo(_) ->
ok = meck:new(emqx_vm, [passthrough, no_history]),
ok = meck:expect(emqx_vm, get_process_info, fun(_) -> [] end),
ok = meck:expect(emqx_vm, get_process_gc_info, fun(_) -> [] end),
?assertEqual([{pid, undefined}], emqx_sys_mon:procinfo(undefined)),
ok = meck:expect(emqx_vm, get_process_info, fun(_) -> [] end), ok = meck:expect(emqx_vm, get_process_info, fun(_) -> [] end),
ok = meck:expect(emqx_vm, get_process_gc_info, fun(_) -> undefined end), ok = meck:expect(emqx_vm, get_process_gc_info, fun(_) -> undefined end),
?assertEqual([{pid, self()}], emqx_sys_mon:procinfo(self())), ?assertEqual([{pid, self()}], emqx_sys_mon:procinfo(self())).
ok = meck:unload(emqx_vm).
t_procinfo_initial_call_and_stacktrace(_) ->
SomePid = proc_lib:spawn(?MODULE, some_function, [self(), arg2]),
receive
{spawned, SomePid} ->
ok
after 100 ->
error(process_not_spawned)
end,
ProcInfo = emqx_sys_mon:procinfo(SomePid),
?assertEqual(
{?MODULE, some_function, ['Argument__1','Argument__2']},
proplists:get_value(proc_lib_initial_call, ProcInfo)),
?assertMatch(
[{?MODULE, some_function, 2,
[{file, _},
{line, _}]},
{proc_lib, init_p_do_apply, 3,
[{file, _},
{line, _}]}],
proplists:get_value(current_stacktrace, ProcInfo)),
SomePid ! stop.
t_sys_mon(_Config) -> t_sys_mon(_Config) ->
lists:foreach( lists:foreach(
@ -119,3 +144,10 @@ validate_sys_mon_info(PidOrPort, SysMonName, ValidateInfo, InfoOrPort) ->
emqtt:stop(C). emqtt:stop(C).
fmt(Fmt, Args) -> lists:flatten(io_lib:format(Fmt, Args)). fmt(Fmt, Args) -> lists:flatten(io_lib:format(Fmt, Args)).
some_function(Parent, _Arg2) ->
Parent ! {spawned, self()},
receive
stop ->
ok
end.

View File

@ -22,7 +22,7 @@
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/emqx.hrl").
-import(emqx_trace_handler_SUITE, [filesync/2]).
-record(emqx_trace, {name, type, filter, enable = true, start_at, end_at}). -record(emqx_trace, {name, type, filter, enable = true, start_at, end_at}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -237,15 +237,16 @@ t_client_event(_Config) ->
emqtt:ping(Client), emqtt:ping(Client),
ok = emqtt:publish(Client, <<"/test">>, #{}, <<"1">>, [{qos, 0}]), ok = emqtt:publish(Client, <<"/test">>, #{}, <<"1">>, [{qos, 0}]),
ok = emqtt:publish(Client, <<"/test">>, #{}, <<"2">>, [{qos, 0}]), ok = emqtt:publish(Client, <<"/test">>, #{}, <<"2">>, [{qos, 0}]),
ct:sleep(200),
ok = emqx_trace:create([{<<"name">>, <<"test_topic">>}, ok = emqx_trace:create([{<<"name">>, <<"test_topic">>},
{<<"type">>, <<"topic">>}, {<<"topic">>, <<"/test">>}, {<<"start_at">>, Start}]), {<<"type">>, <<"topic">>}, {<<"topic">>, <<"/test">>}, {<<"start_at">>, Start}]),
ct:sleep(200), ok = filesync(Name, clientid),
ok = filesync(<<"test_topic">>, topic),
{ok, Bin} = file:read_file(emqx_trace:log_file(Name, Now)), {ok, Bin} = file:read_file(emqx_trace:log_file(Name, Now)),
ok = emqtt:publish(Client, <<"/test">>, #{}, <<"3">>, [{qos, 0}]), ok = emqtt:publish(Client, <<"/test">>, #{}, <<"3">>, [{qos, 0}]),
ok = emqtt:publish(Client, <<"/test">>, #{}, <<"4">>, [{qos, 0}]), ok = emqtt:publish(Client, <<"/test">>, #{}, <<"4">>, [{qos, 0}]),
ok = emqtt:disconnect(Client), ok = emqtt:disconnect(Client),
ct:sleep(200), ok = filesync(Name, clientid),
ok = filesync(<<"test_topic">>, topic),
{ok, Bin2} = file:read_file(emqx_trace:log_file(Name, Now)), {ok, Bin2} = file:read_file(emqx_trace:log_file(Name, Now)),
{ok, Bin3} = file:read_file(emqx_trace:log_file(<<"test_topic">>, Now)), {ok, Bin3} = file:read_file(emqx_trace:log_file(<<"test_topic">>, Now)),
ct:pal("Bin ~p Bin2 ~p Bin3 ~p", [byte_size(Bin), byte_size(Bin2), byte_size(Bin3)]), ct:pal("Bin ~p Bin2 ~p Bin3 ~p", [byte_size(Bin), byte_size(Bin2), byte_size(Bin3)]),
@ -301,7 +302,7 @@ t_download_log(_Config) ->
{ok, Client} = emqtt:start_link([{clean_start, true}, {clientid, ClientId}]), {ok, Client} = emqtt:start_link([{clean_start, true}, {clientid, ClientId}]),
{ok, _} = emqtt:connect(Client), {ok, _} = emqtt:connect(Client),
[begin _ = emqtt:ping(Client) end ||_ <- lists:seq(1, 5)], [begin _ = emqtt:ping(Client) end ||_ <- lists:seq(1, 5)],
ct:sleep(100), ok = filesync(Name, clientid),
{ok, ZipFile} = emqx_trace_api:download_zip_log(#{name => Name}, []), {ok, ZipFile} = emqx_trace_api:download_zip_log(#{name => Name}, []),
?assert(filelib:file_size(ZipFile) > 0), ?assert(filelib:file_size(ZipFile) > 0),
ok = emqtt:disconnect(Client), ok = emqtt:disconnect(Client),

View File

@ -62,7 +62,9 @@ t_trace_clientid(_Config) ->
emqx_trace_handler:install(clientid, <<"client4">>, bad_level, "tmp/client4.log"), emqx_trace_handler:install(clientid, <<"client4">>, bad_level, "tmp/client4.log"),
{error, {handler_not_added, {file_error, ".", eisdir}}} = {error, {handler_not_added, {file_error, ".", eisdir}}} =
emqx_trace_handler:install(clientid, <<"client5">>, debug, "."), emqx_trace_handler:install(clientid, <<"client5">>, debug, "."),
ct:sleep(100), ok = filesync(<<"client">>, clientid),
ok = filesync(<<"client2">>, clientid),
ok = filesync(<<"client3">>, clientid),
%% Verify the tracing file exits %% Verify the tracing file exits
?assert(filelib:is_regular("tmp/client.log")), ?assert(filelib:is_regular("tmp/client.log")),
@ -83,7 +85,10 @@ t_trace_clientid(_Config) ->
emqtt:connect(T), emqtt:connect(T),
emqtt:publish(T, <<"a/b/c">>, <<"hi">>), emqtt:publish(T, <<"a/b/c">>, <<"hi">>),
emqtt:ping(T), emqtt:ping(T),
ct:sleep(200),
ok = filesync(<<"client">>, clientid),
ok = filesync(<<"client2">>, clientid),
ok = filesync(<<"client3">>, clientid),
%% Verify messages are logged to "tmp/client.log" but not "tmp/client2.log". %% Verify messages are logged to "tmp/client.log" but not "tmp/client2.log".
{ok, Bin} = file:read_file("tmp/client.log"), {ok, Bin} = file:read_file("tmp/client.log"),
@ -109,7 +114,8 @@ t_trace_topic(_Config) ->
emqx_logger:set_log_level(debug), emqx_logger:set_log_level(debug),
ok = emqx_trace_handler:install(topic, <<"x/#">>, all, "tmp/topic_trace_x.log"), ok = emqx_trace_handler:install(topic, <<"x/#">>, all, "tmp/topic_trace_x.log"),
ok = emqx_trace_handler:install(topic, <<"y/#">>, all, "tmp/topic_trace_y.log"), ok = emqx_trace_handler:install(topic, <<"y/#">>, all, "tmp/topic_trace_y.log"),
ct:sleep(100), ok = filesync(<<"x/#">>, topic),
ok = filesync(<<"y/#">>, topic),
%% Verify the tracing file exits %% Verify the tracing file exits
?assert(filelib:is_regular("tmp/topic_trace_x.log")), ?assert(filelib:is_regular("tmp/topic_trace_x.log")),
@ -128,7 +134,8 @@ t_trace_topic(_Config) ->
emqtt:publish(T, <<"x/y/z">>, <<"hi2">>), emqtt:publish(T, <<"x/y/z">>, <<"hi2">>),
emqtt:subscribe(T, <<"x/y/z">>), emqtt:subscribe(T, <<"x/y/z">>),
emqtt:unsubscribe(T, <<"x/y/z">>), emqtt:unsubscribe(T, <<"x/y/z">>),
ct:sleep(200), ok = filesync(<<"x/#">>, topic),
ok = filesync(<<"y/#">>, topic),
{ok, Bin} = file:read_file("tmp/topic_trace_x.log"), {ok, Bin} = file:read_file("tmp/topic_trace_x.log"),
?assertNotEqual(nomatch, binary:match(Bin, [<<"hi1">>])), ?assertNotEqual(nomatch, binary:match(Bin, [<<"hi1">>])),
@ -152,8 +159,8 @@ t_trace_ip_address(_Config) ->
%% Start tracing %% Start tracing
ok = emqx_trace_handler:install(ip_address, "127.0.0.1", all, "tmp/ip_trace_x.log"), ok = emqx_trace_handler:install(ip_address, "127.0.0.1", all, "tmp/ip_trace_x.log"),
ok = emqx_trace_handler:install(ip_address, "192.168.1.1", all, "tmp/ip_trace_y.log"), ok = emqx_trace_handler:install(ip_address, "192.168.1.1", all, "tmp/ip_trace_y.log"),
ct:sleep(100), ok = filesync(<<"127.0.0.1">>, ip_address),
ok = filesync(<<"192.168.1.1">>, ip_address),
%% Verify the tracing file exits %% Verify the tracing file exits
?assert(filelib:is_regular("tmp/ip_trace_x.log")), ?assert(filelib:is_regular("tmp/ip_trace_x.log")),
?assert(filelib:is_regular("tmp/ip_trace_y.log")), ?assert(filelib:is_regular("tmp/ip_trace_y.log")),
@ -173,7 +180,8 @@ t_trace_ip_address(_Config) ->
emqtt:publish(T, <<"x/y/z">>, <<"hi2">>), emqtt:publish(T, <<"x/y/z">>, <<"hi2">>),
emqtt:subscribe(T, <<"x/y/z">>), emqtt:subscribe(T, <<"x/y/z">>),
emqtt:unsubscribe(T, <<"x/y/z">>), emqtt:unsubscribe(T, <<"x/y/z">>),
ct:sleep(200), ok = filesync(<<"127.0.0.1">>, ip_address),
ok = filesync(<<"192.168.1.1">>, ip_address),
{ok, Bin} = file:read_file("tmp/ip_trace_x.log"), {ok, Bin} = file:read_file("tmp/ip_trace_x.log"),
?assertNotEqual(nomatch, binary:match(Bin, [<<"hi1">>])), ?assertNotEqual(nomatch, binary:match(Bin, [<<"hi1">>])),
@ -189,3 +197,19 @@ t_trace_ip_address(_Config) ->
{error, _Reason} = emqx_trace_handler:uninstall(ip_address, <<"127.0.0.2">>), {error, _Reason} = emqx_trace_handler:uninstall(ip_address, <<"127.0.0.2">>),
emqtt:disconnect(T), emqtt:disconnect(T),
?assertEqual([], emqx_trace_handler:running()). ?assertEqual([], emqx_trace_handler:running()).
filesync(Name, Type) ->
filesync(Name, Type, 3).
%% sometime the handler process is not started yet.
filesync(_Name, _Type, 0) -> ok;
filesync(Name, Type, Retry) ->
try
Handler = binary_to_atom(<<"trace_",
(atom_to_binary(Type))/binary, "_", Name/binary>>),
ok = logger_disk_log_h:filesync(Handler)
catch E:R ->
ct:pal("Filesync error:~p ~p~n", [{Name, Type, Retry}, {E, R}]),
ct:sleep(100),
filesync(Name, Type, Retry - 1)
end.

View File

@ -21,6 +21,7 @@
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-include("emqx_authn.hrl"). -include("emqx_authn.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl"). -include_lib("emqx/include/emqx_placeholder.hrl").
-include_lib("emqx/include/logger.hrl").
-import(hoconsc, [mk/2, ref/1]). -import(hoconsc, [mk/2, ref/1]).
-import(emqx_dashboard_swagger, [error_codes/2]). -import(emqx_dashboard_swagger, [error_codes/2]).
@ -590,7 +591,7 @@ listener_authenticator(delete,
authenticator_move(post, authenticator_move(post,
#{bindings := #{id := AuthenticatorID}, #{bindings := #{id := AuthenticatorID},
body := #{<<"position">> := Position}}) -> body := #{<<"position">> := Position}}) ->
move_authenitcator([authentication], ?GLOBAL, AuthenticatorID, Position); move_authenticator([authentication], ?GLOBAL, AuthenticatorID, Position);
authenticator_move(post, #{bindings := #{id := _}, body := _}) -> authenticator_move(post, #{bindings := #{id := _}, body := _}) ->
serialize_error({missing_parameter, position}). serialize_error({missing_parameter, position}).
@ -599,7 +600,7 @@ listener_authenticator_move(post,
body := #{<<"position">> := Position}}) -> body := #{<<"position">> := Position}}) ->
with_listener(ListenerID, with_listener(ListenerID,
fun(Type, Name, ChainName) -> fun(Type, Name, ChainName) ->
move_authenitcator([listeners, Type, Name, authentication], move_authenticator([listeners, Type, Name, authentication],
ChainName, ChainName,
AuthenticatorID, AuthenticatorID,
Position) Position)
@ -725,7 +726,9 @@ create_authenticator(ConfKeyPath, ChainName, Config) ->
raw_config := AuthenticatorsConfig}} -> raw_config := AuthenticatorsConfig}} ->
{ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig), {ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig),
{200, maps:put(id, ID, convert_certs(fill_defaults(AuthenticatorConfig)))}; {200, maps:put(id, ID, convert_certs(fill_defaults(AuthenticatorConfig)))};
{error, {_, _, Reason}} -> {error, {_PrePostConfigUpdate, emqx_authentication, Reason}} ->
serialize_error(Reason);
{error, Reason} ->
serialize_error(Reason) serialize_error(Reason)
end. end.
@ -753,7 +756,9 @@ update_authenticator(ConfKeyPath, ChainName, AuthenticatorID, Config) ->
raw_config := AuthenticatorsConfig}} -> raw_config := AuthenticatorsConfig}} ->
{ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig), {ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig),
{200, maps:put(id, ID, convert_certs(fill_defaults(AuthenticatorConfig)))}; {200, maps:put(id, ID, convert_certs(fill_defaults(AuthenticatorConfig)))};
{error, {_, _, Reason}} -> {error, {_PrePostConfigUpdate, emqx_authentication, Reason}} ->
serialize_error(Reason);
{error, Reason} ->
serialize_error(Reason) serialize_error(Reason)
end. end.
@ -761,11 +766,13 @@ delete_authenticator(ConfKeyPath, ChainName, AuthenticatorID) ->
case update_config(ConfKeyPath, {delete_authenticator, ChainName, AuthenticatorID}) of case update_config(ConfKeyPath, {delete_authenticator, ChainName, AuthenticatorID}) of
{ok, _} -> {ok, _} ->
{204}; {204};
{error, {_, _, Reason}} -> {error, {_PrePostConfigUpdate, emqx_authentication, Reason}} ->
serialize_error(Reason);
{error, Reason} ->
serialize_error(Reason) serialize_error(Reason)
end. end.
move_authenitcator(ConfKeyPath, ChainName, AuthenticatorID, Position) -> move_authenticator(ConfKeyPath, ChainName, AuthenticatorID, Position) ->
case parse_position(Position) of case parse_position(Position) of
{ok, NPosition} -> {ok, NPosition} ->
case update_config( case update_config(
@ -773,7 +780,9 @@ move_authenitcator(ConfKeyPath, ChainName, AuthenticatorID, Position) ->
{move_authenticator, ChainName, AuthenticatorID, NPosition}) of {move_authenticator, ChainName, AuthenticatorID, NPosition}) of
{ok, _} -> {ok, _} ->
{204}; {204};
{error, {_, _, Reason}} -> {error, {_PrePostConfigUpdate, emqx_authentication, Reason}} ->
serialize_error(Reason);
{error, Reason} ->
serialize_error(Reason) serialize_error(Reason)
end; end;
{error, Reason} -> {error, Reason} ->

View File

@ -160,13 +160,15 @@ authenticate(Credential, #{resource_id := ResourceId,
Request = generate_request(Credential, State), Request = generate_request(Credential, State),
case emqx_resource:query(ResourceId, {Method, Request, RequestTimeout}) of case emqx_resource:query(ResourceId, {Method, Request, RequestTimeout}) of
{ok, 204, _Headers} -> {ok, #{is_superuser => false}}; {ok, 204, _Headers} -> {ok, #{is_superuser => false}};
{ok, 200, _Headers} -> {ok, #{is_superuser => false}};
{ok, 200, Headers, Body} -> {ok, 200, Headers, Body} ->
ContentType = proplists:get_value(<<"content-type">>, Headers, <<"application/json">>), ContentType = proplists:get_value(<<"content-type">>, Headers, <<"application/json">>),
case safely_parse_body(ContentType, Body) of case safely_parse_body(ContentType, Body) of
{ok, NBody} -> {ok, NBody} ->
%% TODO: Return by user property %% TODO: Return by user property
{ok, #{is_superuser => maps:get(<<"is_superuser">>, NBody, false), UserProperty = maps:remove(<<"is_superuser">>, NBody),
user_property => maps:remove(<<"is_superuser">>, NBody)}}; IsSuperuser = emqx_authn_utils:is_superuser(NBody),
{ok, IsSuperuser#{user_property => UserProperty}};
{error, _Reason} -> {error, _Reason} ->
{ok, #{is_superuser => false}} {ok, #{is_superuser => false}}
end; end;
@ -208,9 +210,9 @@ check_url(URL) ->
end. end.
check_body(Body) -> check_body(Body) ->
maps:fold(fun(_K, _V, false) -> false; lists:all(
(_K, V, true) -> is_binary(V) fun erlang:is_binary/1,
end, true, Body). maps:values(Body)).
default_headers() -> default_headers() ->
maps:put(<<"content-type">>, maps:put(<<"content-type">>,
@ -242,12 +244,9 @@ check_ssl_opts(Conf) ->
end. end.
check_headers(Conf) -> check_headers(Conf) ->
Method = hocon_schema:get_value("config.method", Conf), Method = to_bin(hocon_schema:get_value("config.method", Conf)),
Headers = hocon_schema:get_value("config.headers", Conf), Headers = hocon_schema:get_value("config.headers", Conf),
case Method =:= get andalso maps:get(<<"content-type">>, Headers, undefined) =/= undefined of Method =:= <<"post">> orelse (not maps:is_key(<<"content-type">>, Headers)).
true -> false;
false -> true
end.
parse_url(URL) -> parse_url(URL) ->
{ok, URIMap} = emqx_http_lib:uri_parse(URL), {ok, URIMap} = emqx_http_lib:uri_parse(URL),
@ -300,7 +299,7 @@ qs([], Acc) ->
<<$&, Qs/binary>> = iolist_to_binary(lists:reverse(Acc)), <<$&, Qs/binary>> = iolist_to_binary(lists:reverse(Acc)),
Qs; Qs;
qs([{K, V} | More], Acc) -> qs([{K, V} | More], Acc) ->
qs(More, [["&", emqx_http_lib:uri_encode(K), "=", emqx_http_lib:uri_encode(V)] | Acc]). qs(More, [["&", uri_encode(K), "=", uri_encode(V)] | Acc]).
serialize_body(<<"application/json">>, Body) -> serialize_body(<<"application/json">>, Body) ->
emqx_json:encode(Body); emqx_json:encode(Body);
@ -327,7 +326,17 @@ may_append_body(Output, {ok, _, _, Body}) ->
may_append_body(Output, {ok, _, _}) -> may_append_body(Output, {ok, _, _}) ->
Output. Output.
uri_encode(T) ->
emqx_http_lib:uri_encode(to_bin(T)).
to_list(A) when is_atom(A) -> to_list(A) when is_atom(A) ->
atom_to_list(A); atom_to_list(A);
to_list(B) when is_binary(B) -> to_list(B) when is_binary(B) ->
binary_to_list(B). binary_to_list(B).
to_bin(A) when is_atom(A) ->
atom_to_binary(A);
to_bin(B) when is_binary(B) ->
B;
to_bin(L) when is_list(L) ->
list_to_binary(L).

View File

@ -125,7 +125,7 @@ verify_claims(default) -> #{};
verify_claims(validator) -> [fun do_check_verify_claims/1]; verify_claims(validator) -> [fun do_check_verify_claims/1];
verify_claims(converter) -> verify_claims(converter) ->
fun(VerifyClaims) -> fun(VerifyClaims) ->
maps:to_list(VerifyClaims) [{to_binary(K), V} || {K, V} <- maps:to_list(VerifyClaims)]
end; end;
verify_claims(_) -> undefined. verify_claims(_) -> undefined.
@ -201,15 +201,14 @@ create2(#{use_jwks := false,
secret := Secret0, secret := Secret0,
secret_base64_encoded := Base64Encoded, secret_base64_encoded := Base64Encoded,
verify_claims := VerifyClaims}) -> verify_claims := VerifyClaims}) ->
Secret = case Base64Encoded of case may_decode_secret(Base64Encoded, Secret0) of
true -> {error, Reason} ->
base64:decode(Secret0); {error, Reason};
false -> Secret ->
Secret0 JWK = jose_jwk:from_oct(Secret),
end, {ok, #{jwk => JWK,
JWK = jose_jwk:from_oct(Secret), verify_claims => VerifyClaims}}
{ok, #{jwk => JWK, end;
verify_claims => VerifyClaims}};
create2(#{use_jwks := false, create2(#{use_jwks := false,
algorithm := 'public-key', algorithm := 'public-key',
@ -234,6 +233,14 @@ create2(#{use_jwks := true,
{error, Reason} {error, Reason}
end. end.
may_decode_secret(false, Secret) -> Secret;
may_decode_secret(true, Secret) ->
try base64:decode(Secret)
catch
error : _ ->
{error, {invalid_parameter, secret}}
end.
replace_placeholder(L, Variables) -> replace_placeholder(L, Variables) ->
replace_placeholder(L, Variables, []). replace_placeholder(L, Variables, []).
@ -349,3 +356,8 @@ validate_placeholder(<<"clientid">>) ->
clientid; clientid;
validate_placeholder(<<"username">>) -> validate_placeholder(<<"username">>) ->
username. username.
to_binary(A) when is_atom(A) ->
atom_to_binary(A);
to_binary(B) when is_binary(B) ->
B.

View File

@ -134,11 +134,23 @@ test_authenticators(PathPrefix) ->
uri(PathPrefix ++ ["authentication"]), uri(PathPrefix ++ ["authentication"]),
ValidConfig), ValidConfig),
InvalidConfig = ValidConfig#{method => <<"delete">>}, {ok, 409, _} = request(
post,
uri(PathPrefix ++ ["authentication"]),
ValidConfig),
InvalidConfig0 = ValidConfig#{method => <<"delete">>},
{ok, 400, _} = request( {ok, 400, _} = request(
post, post,
uri(PathPrefix ++ ["authentication"]), uri(PathPrefix ++ ["authentication"]),
InvalidConfig), InvalidConfig0),
InvalidConfig1 = ValidConfig#{method => <<"get">>,
headers => #{<<"content-type">> => <<"application/json">>}},
{ok, 400, _} = request(
post,
uri(PathPrefix ++ ["authentication"]),
InvalidConfig1),
?assertAuthenticatorsMatch( ?assertAuthenticatorsMatch(
[#{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}], [#{<<"mechanism">> := <<"password-based">>, <<"backend">> := <<"http">>}],
@ -170,6 +182,13 @@ test_authenticator(PathPrefix) ->
uri(PathPrefix ++ ["authentication", "password-based:http"]), uri(PathPrefix ++ ["authentication", "password-based:http"]),
InvalidConfig0), InvalidConfig0),
InvalidConfig1 = ValidConfig0#{method => <<"get">>,
headers => #{<<"content-type">> => <<"application/json">>}},
{ok, 400, _} = request(
put,
uri(PathPrefix ++ ["authentication", "password-based:http"]),
InvalidConfig1),
ValidConfig1 = ValidConfig0#{pool_size => 9}, ValidConfig1 = ValidConfig0#{pool_size => 9},
{ok, 200, _} = request( {ok, 200, _} = request(
put, put,

View File

@ -0,0 +1,392 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_authn_http_SUITE).
-compile(nowarn_export_all).
-compile(export_all).
-include("emqx_authn.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
-define(PATH, [authentication]).
-define(HTTP_PORT, 33333).
-define(HTTP_PATH, "/auth").
-define(CREDENTIALS, #{username => <<"plain">>,
password => <<"plain">>,
listener => 'tcp:default',
protocol => mqtt
}).
all() ->
emqx_common_test_helpers:all(?MODULE).
init_per_suite(Config) ->
emqx_common_test_helpers:start_apps([emqx_authn]),
application:ensure_all_started(cowboy),
Config.
end_per_suite(_) ->
emqx_authn_test_lib:delete_authenticators(
[authentication],
?GLOBAL),
emqx_common_test_helpers:stop_apps([emqx_authn]),
application:stop(cowboy),
ok.
init_per_testcase(_Case, Config) ->
emqx_authn_test_lib:delete_authenticators(
[authentication],
?GLOBAL),
emqx_authn_http_test_server:start(?HTTP_PORT, ?HTTP_PATH),
Config.
end_per_testcase(_Case, _Config) ->
ok = emqx_authn_http_test_server:stop() .
%%------------------------------------------------------------------------------
%% Tests
%%------------------------------------------------------------------------------
t_create(_Config) ->
AuthConfig = raw_http_auth_config(),
{ok, _} = emqx:update_config(
?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}),
{ok, [#{provider := emqx_authn_http}]} = emqx_authentication:list_authenticators(?GLOBAL).
t_create_invalid(_Config) ->
AuthConfig = raw_http_auth_config(),
InvalidConfigs =
[
AuthConfig#{headers => []},
AuthConfig#{method => delete}
],
lists:foreach(
fun(Config) ->
ct:pal("creating authenticator with invalid config: ~p", [Config]),
{error, _} =
try
emqx:update_config(
?PATH,
{create_authenticator, ?GLOBAL, Config})
catch
throw:Error ->
{error, Error}
end,
{ok, []} = emqx_authentication:list_authenticators(?GLOBAL)
end,
InvalidConfigs).
t_authenticate(_Config) ->
ok = lists:foreach(
fun(Sample) ->
ct:pal("test_user_auth sample: ~p", [Sample]),
test_user_auth(Sample)
end,
samples()).
test_user_auth(#{handler := Handler,
config_params := SpecificConfgParams,
result := Result}) ->
AuthConfig = maps:merge(raw_http_auth_config(), SpecificConfgParams),
{ok, _} = emqx:update_config(
?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}),
emqx_authn_http_test_server:set_handler(Handler),
?assertEqual(Result, emqx_access_control:authenticate(?CREDENTIALS)),
emqx_authn_test_lib:delete_authenticators(
[authentication],
?GLOBAL).
t_destroy(_Config) ->
AuthConfig = raw_http_auth_config(),
{ok, _} = emqx:update_config(
?PATH,
{create_authenticator, ?GLOBAL, AuthConfig}),
ok = emqx_authn_http_test_server:set_handler(
fun(Req0, State) ->
Req = cowboy_req:reply(200, Req0),
{ok, Req, State}
end),
{ok, [#{provider := emqx_authn_http, state := State}]}
= emqx_authentication:list_authenticators(?GLOBAL),
Credentials = maps:with([username, password], ?CREDENTIALS),
{ok, _} = emqx_authn_http:authenticate(
Credentials,
State),
emqx_authn_test_lib:delete_authenticators(
[authentication],
?GLOBAL),
% Authenticator should not be usable anymore
?assertException(
error,
_,
emqx_authn_http:authenticate(
Credentials,
State)).
t_update(_Config) ->
CorrectConfig = raw_http_auth_config(),
IncorrectConfig =
CorrectConfig#{url => <<"http://127.0.0.1:33333/invalid">>},
{ok, _} = emqx:update_config(
?PATH,
{create_authenticator, ?GLOBAL, IncorrectConfig}),
ok = emqx_authn_http_test_server:set_handler(
fun(Req0, State) ->
Req = cowboy_req:reply(200, Req0),
{ok, Req, State}
end),
{error, not_authorized} = emqx_access_control:authenticate(?CREDENTIALS),
% We update with config with correct query, provider should update and work properly
{ok, _} = emqx:update_config(
?PATH,
{update_authenticator, ?GLOBAL, <<"password-based:http">>, CorrectConfig}),
{ok,_} = emqx_access_control:authenticate(?CREDENTIALS).
t_is_superuser(_Config) ->
Config = raw_http_auth_config(),
{ok, _} = emqx:update_config(
?PATH,
{create_authenticator, ?GLOBAL, Config}),
Checks = [
{json, <<"0">>, false},
{json, <<"">>, false},
{json, null, false},
{json, 0, false},
{json, <<"1">>, true},
{json, <<"val">>, true},
{json, 1, true},
{json, 123, true},
{form, <<"0">>, false},
{form, <<"">>, false},
{form, <<"1">>, true},
{form, <<"val">>, true}
],
lists:foreach(fun test_is_superuser/1, Checks).
test_is_superuser({Kind, Value, ExpectedValue}) ->
{ContentType, Res} = case Kind of
json ->
{<<"application/json">>,
jiffy:encode(#{is_superuser => Value})};
form ->
{<<"application/x-www-form-urlencoded">>,
iolist_to_binary([<<"is_superuser=">>, Value])}
end,
emqx_authn_http_test_server:set_handler(
fun(Req0, State) ->
Req = cowboy_req:reply(
200,
#{<<"content-type">> => ContentType},
Res,
Req0),
{ok, Req, State}
end),
?assertMatch(
{ok, #{is_superuser := ExpectedValue}},
emqx_access_control:authenticate(?CREDENTIALS)).
%%------------------------------------------------------------------------------
%% Helpers
%%------------------------------------------------------------------------------
raw_http_auth_config() ->
#{
mechanism => <<"password-based">>,
enable => <<"true">>,
backend => <<"http">>,
method => <<"get">>,
url => <<"http://127.0.0.1:33333/auth">>,
body => #{<<"username">> => ?PH_USERNAME, <<"password">> => ?PH_PASSWORD},
headers => #{<<"X-Test-Header">> => <<"Test Value">>}
}.
samples() ->
[
%% simple get request
#{handler => fun(Req0, State) ->
#{username := <<"plain">>,
password := <<"plain">>
} = cowboy_req:match_qs([username, password], Req0),
Req = cowboy_req:reply(200, Req0),
{ok, Req, State}
end,
config_params => #{},
result => {ok,#{is_superuser => false}}
},
%% get request with json body response
#{handler => fun(Req0, State) ->
Req = cowboy_req:reply(
200,
#{<<"content-type">> => <<"application/json">>},
jiffy:encode(#{is_superuser => true}),
Req0),
{ok, Req, State}
end,
config_params => #{},
result => {ok,#{is_superuser => true, user_property => #{}}}
},
%% get request with url-form-encoded body response
#{handler => fun(Req0, State) ->
Req = cowboy_req:reply(
200,
#{<<"content-type">> =>
<<"application/x-www-form-urlencoded">>},
<<"is_superuser=true">>,
Req0),
{ok, Req, State}
end,
config_params => #{},
result => {ok,#{is_superuser => true, user_property => #{}}}
},
%% get request with response of unknown encoding
#{handler => fun(Req0, State) ->
Req = cowboy_req:reply(
200,
#{<<"content-type">> =>
<<"test/plain">>},
<<"is_superuser=true">>,
Req0),
{ok, Req, State}
end,
config_params => #{},
result => {ok,#{is_superuser => false}}
},
%% simple post request, application/json
#{handler => fun(Req0, State) ->
{ok, RawBody, Req1} = cowboy_req:read_body(Req0),
#{<<"username">> := <<"plain">>,
<<"password">> := <<"plain">>
} = jiffy:decode(RawBody, [return_maps]),
Req = cowboy_req:reply(200, Req1),
{ok, Req, State}
end,
config_params => #{
method => post,
headers => #{<<"content-type">> => <<"application/json">>}
},
result => {ok,#{is_superuser => false}}
},
%% simple post request, application/x-www-form-urlencoded
#{handler => fun(Req0, State) ->
{ok, PostVars, Req1} = cowboy_req:read_urlencoded_body(Req0),
#{<<"username">> := <<"plain">>,
<<"password">> := <<"plain">>
} = maps:from_list(PostVars),
Req = cowboy_req:reply(200, Req1),
{ok, Req, State}
end,
config_params => #{
method => post,
headers => #{<<"content-type">> =>
<<"application/x-www-form-urlencoded">>}
},
result => {ok,#{is_superuser => false}}
}
%% 204 code
#{handler => fun(Req0, State) ->
Req = cowboy_req:reply(204, Req0),
{ok, Req, State}
end,
config_params => #{},
result => {ok,#{is_superuser => false}}
},
%% custom headers
#{handler => fun(Req0, State) ->
<<"Test Value">> = cowboy_req:header(<<"x-test-header">>, Req0),
Req = cowboy_req:reply(200, Req0),
{ok, Req, State}
end,
config_params => #{},
result => {ok,#{is_superuser => false}}
},
%% 400 code
#{handler => fun(Req0, State) ->
Req = cowboy_req:reply(400, Req0),
{ok, Req, State}
end,
config_params => #{},
result => {error,not_authorized}
},
%% 500 code
#{handler => fun(Req0, State) ->
Req = cowboy_req:reply(500, Req0),
{ok, Req, State}
end,
config_params => #{},
result => {error,not_authorized}
},
%% Handling error
#{handler => fun(Req0, State) ->
error(woops),
{ok, Req0, State}
end,
config_params => #{},
result => {error,not_authorized}
}
].
start_apps(Apps) ->
lists:foreach(fun application:ensure_all_started/1, Apps).
stop_apps(Apps) ->
lists:foreach(fun application:stop/1, Apps).

View File

@ -0,0 +1,89 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_authn_http_test_server).
-behaviour(gen_server).
-behaviour(cowboy_handler).
% cowboy_server callbacks
-export([init/2]).
% gen_server callbacks
-export([init/1,
handle_call/3,
handle_cast/2
]).
% API
-export([start/2,
stop/0,
set_handler/1
]).
%%------------------------------------------------------------------------------
%% API
%%------------------------------------------------------------------------------
start(Port, Path) ->
Dispatch = cowboy_router:compile([
{'_', [{Path, ?MODULE, []}]}
]),
{ok, _} = cowboy:start_clear(?MODULE,
[{port, Port}],
#{env => #{dispatch => Dispatch}}
),
{ok, _} = gen_server:start_link({local, ?MODULE}, ?MODULE, [], []),
ok.
stop() ->
gen_server:stop(?MODULE),
cowboy:stop_listener(?MODULE).
set_handler(F) when is_function(F, 2) ->
gen_server:call(?MODULE, {set_handler, F}).
%%------------------------------------------------------------------------------
%% gen_server API
%%------------------------------------------------------------------------------
init([]) ->
F = fun(Req0, State) ->
Req = cowboy_req:reply(
400,
#{<<"content-type">> => <<"text/plain">>},
<<"">>,
Req0),
{ok, Req, State}
end,
{ok, F}.
handle_cast(_, F) ->
{noreply, F}.
handle_call({set_handler, F}, _From, _F) ->
{reply, ok, F};
handle_call(get_handler, _From, F) ->
{reply, F, F}.
%%------------------------------------------------------------------------------
%% cowboy_server API
%%------------------------------------------------------------------------------
init(Req, State) ->
Handler = gen_server:call(?MODULE, get_handler),
Handler(Req, State).

View File

@ -19,140 +19,142 @@
-compile(export_all). -compile(export_all).
-compile(nowarn_export_all). -compile(nowarn_export_all).
% -include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
% -include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
% -include("emqx_authn.hrl"). -include("emqx_authn.hrl").
% -define(AUTH, emqx_authn). -define(AUTHN_ID, <<"mechanism:jwt">>).
all() -> all() ->
emqx_common_test_helpers:all(?MODULE). emqx_common_test_helpers:all(?MODULE).
% init_per_suite(Config) -> init_per_suite(Config) ->
% emqx_common_test_helpers:start_apps([emqx_authn]), emqx_common_test_helpers:start_apps([emqx_authn]),
% Config. Config.
% end_per_suite(_) -> end_per_suite(_) ->
% emqx_common_test_helpers:stop_apps([emqx_authn]), emqx_common_test_helpers:stop_apps([emqx_authn]),
% ok. ok.
% t_jwt_authenticator(_) -> t_jwt_authenticator(_) ->
% AuthenticatorName = <<"myauthenticator">>, Secret = <<"abcdef">>,
% Config = #{name => AuthenticatorName, Config = #{mechanism => jwt,
% mechanism => jwt, use_jwks => false,
% use_jwks => false, algorithm => 'hmac-based',
% algorithm => 'hmac-based', secret => Secret,
% secret => <<"abcdef">>, secret_base64_encoded => false,
% secret_base64_encoded => false, verify_claims => []},
% verify_claims => []}, {ok, State} = emqx_authn_jwt:create(?AUTHN_ID, Config),
% {ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, Config),
% Payload = #{<<"username">> => <<"myuser">>}, Payload = #{<<"username">> => <<"myuser">>},
% JWS = generate_jws('hmac-based', Payload, <<"abcdef">>), JWS = generate_jws('hmac-based', Payload, Secret),
% ClientInfo = #{username => <<"myuser">>, Credential = #{username => <<"myuser">>,
% password => JWS}, password => JWS},
% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State)),
% Payload1 = #{<<"username">> => <<"myuser">>, <<"is_superuser">> => true}, Payload1 = #{<<"username">> => <<"myuser">>, <<"is_superuser">> => true},
% JWS1 = generate_jws('hmac-based', Payload1, <<"abcdef">>), JWS1 = generate_jws('hmac-based', Payload1, Secret),
% ClientInfo1 = #{username => <<"myuser">>, Credential1 = #{username => <<"myuser">>,
% password => JWS1}, password => JWS1},
% ?assertEqual({stop, {ok, #{is_superuser => true}}}, ?AUTH:authenticate(ClientInfo1, ignored)), ?assertEqual({ok, #{is_superuser => true}}, emqx_authn_jwt:authenticate(Credential1, State)),
% BadJWS = generate_jws('hmac-based', Payload, <<"bad_secret">>), BadJWS = generate_jws('hmac-based', Payload, <<"bad_secret">>),
% ClientInfo2 = ClientInfo#{password => BadJWS}, Credential2 = Credential#{password => BadJWS},
% ?assertEqual({stop, {error, not_authorized}}, ?AUTH:authenticate(ClientInfo2, ignored)), ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential2, State)),
% %% secret_base64_encoded %% secret_base64_encoded
% Config2 = Config#{secret => base64:encode(<<"abcdef">>), Config2 = Config#{secret => base64:encode(Secret),
% secret_base64_encoded => true}, secret_base64_encoded => true},
% ?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, ID, Config2)), {ok, State2} = emqx_authn_jwt:update(Config2, State),
% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State2)),
% Config3 = Config#{verify_claims => [{<<"username">>, <<"${mqtt-username}">>}]}, %% invalid secret
% ?assertMatch({ok, _}, ?AUTH:update_authenticator(?CHAIN, ID, Config3)), BadConfig = Config#{secret => <<"emqxsecret">>,
% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), secret_base64_encoded => true},
% ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo#{username => <<"otheruser">>}, ok)), {error, {invalid_parameter, secret}} = emqx_authn_jwt:create(?AUTHN_ID, BadConfig),
% %% Expiration Config3 = Config#{verify_claims => [{<<"username">>, <<"${username}">>}]},
% Payload3 = #{ <<"username">> => <<"myuser">> {ok, State3} = emqx_authn_jwt:update(Config3, State2),
% , <<"exp">> => erlang:system_time(second) - 60}, ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State3)),
% JWS3 = generate_jws('hmac-based', Payload3, <<"abcdef">>), ?assertEqual({error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential#{username => <<"otheruser">>}, State3)),
% ClientInfo3 = ClientInfo#{password => JWS3},
% ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo3, ignored)),
% Payload4 = #{ <<"username">> => <<"myuser">> %% Expiration
% , <<"exp">> => erlang:system_time(second) + 60}, Payload3 = #{ <<"username">> => <<"myuser">>
% JWS4 = generate_jws('hmac-based', Payload4, <<"abcdef">>), , <<"exp">> => erlang:system_time(second) - 60},
% ClientInfo4 = ClientInfo#{password => JWS4}, JWS3 = generate_jws('hmac-based', Payload3, Secret),
% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo4, ignored)), Credential3 = Credential#{password => JWS3},
?assertEqual({error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential3, State3)),
% %% Issued At Payload4 = #{ <<"username">> => <<"myuser">>
% Payload5 = #{ <<"username">> => <<"myuser">> , <<"exp">> => erlang:system_time(second) + 60},
% , <<"iat">> => erlang:system_time(second) - 60}, JWS4 = generate_jws('hmac-based', Payload4, Secret),
% JWS5 = generate_jws('hmac-based', Payload5, <<"abcdef">>), Credential4 = Credential#{password => JWS4},
% ClientInfo5 = ClientInfo#{password => JWS5}, ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential4, State3)),
% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo5, ignored)),
% Payload6 = #{ <<"username">> => <<"myuser">> %% Issued At
% , <<"iat">> => erlang:system_time(second) + 60}, Payload5 = #{ <<"username">> => <<"myuser">>
% JWS6 = generate_jws('hmac-based', Payload6, <<"abcdef">>), , <<"iat">> => erlang:system_time(second) - 60},
% ClientInfo6 = ClientInfo#{password => JWS6}, JWS5 = generate_jws('hmac-based', Payload5, Secret),
% ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo6, ignored)), Credential5 = Credential#{password => JWS5},
?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential5, State3)),
% %% Not Before Payload6 = #{ <<"username">> => <<"myuser">>
% Payload7 = #{ <<"username">> => <<"myuser">> , <<"iat">> => erlang:system_time(second) + 60},
% , <<"nbf">> => erlang:system_time(second) - 60}, JWS6 = generate_jws('hmac-based', Payload6, Secret),
% JWS7 = generate_jws('hmac-based', Payload7, <<"abcdef">>), Credential6 = Credential#{password => JWS6},
% ClientInfo7 = ClientInfo#{password => JWS7}, ?assertEqual({error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential6, State3)),
% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo7, ignored)),
% Payload8 = #{ <<"username">> => <<"myuser">> %% Not Before
% , <<"nbf">> => erlang:system_time(second) + 60}, Payload7 = #{ <<"username">> => <<"myuser">>
% JWS8 = generate_jws('hmac-based', Payload8, <<"abcdef">>), , <<"nbf">> => erlang:system_time(second) - 60},
% ClientInfo8 = ClientInfo#{password => JWS8}, JWS7 = generate_jws('hmac-based', Payload7, Secret),
% ?assertEqual({stop, {error, bad_username_or_password}}, ?AUTH:authenticate(ClientInfo8, ignored)), Credential7 = Credential6#{password => JWS7},
?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential7, State3)),
% ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)), Payload8 = #{ <<"username">> => <<"myuser">>
% ok. , <<"nbf">> => erlang:system_time(second) + 60},
JWS8 = generate_jws('hmac-based', Payload8, Secret),
Credential8 = Credential#{password => JWS8},
?assertEqual({error, bad_username_or_password}, emqx_authn_jwt:authenticate(Credential8, State3)),
% t_jwt_authenticator2(_) -> ?assertEqual(ok, emqx_authn_jwt:destroy(State3)),
% Dir = code:lib_dir(emqx_authn, test), ok.
% PublicKey = list_to_binary(filename:join([Dir, "data/public_key.pem"])),
% PrivateKey = list_to_binary(filename:join([Dir, "data/private_key.pem"])),
% AuthenticatorName = <<"myauthenticator">>,
% Config = #{name => AuthenticatorName,
% mechanism => jwt,
% use_jwks => false,
% algorithm => 'public-key',
% certificate => PublicKey,
% verify_claims => []},
% {ok, #{name := AuthenticatorName, id := ID}} = ?AUTH:create_authenticator(?CHAIN, Config),
% Payload = #{<<"username">> => <<"myuser">>}, t_jwt_authenticator2(_) ->
% JWS = generate_jws('public-key', Payload, PrivateKey), Dir = code:lib_dir(emqx_authn, test),
% ClientInfo = #{username => <<"myuser">>, PublicKey = list_to_binary(filename:join([Dir, "data/public_key.pem"])),
% password => JWS}, PrivateKey = list_to_binary(filename:join([Dir, "data/private_key.pem"])),
% ?assertEqual({stop, {ok, #{is_superuser => false}}}, ?AUTH:authenticate(ClientInfo, ignored)), Config = #{mechanism => jwt,
% ?assertEqual({stop, {error, not_authorized}}, ?AUTH:authenticate(ClientInfo#{password => <<"badpassword">>}, ignored)), use_jwks => false,
algorithm => 'public-key',
certificate => PublicKey,
verify_claims => []},
{ok, State} = emqx_authn_jwt:create(?AUTHN_ID, Config),
% ?assertEqual(ok, ?AUTH:delete_authenticator(?CHAIN, ID)), Payload = #{<<"username">> => <<"myuser">>},
% ok. JWS = generate_jws('public-key', Payload, PrivateKey),
Credential = #{username => <<"myuser">>,
password => JWS},
?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State)),
?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State)),
% generate_jws('hmac-based', Payload, Secret) -> ?assertEqual(ok, emqx_authn_jwt:destroy(State)),
% JWK = jose_jwk:from_oct(Secret), ok.
% Header = #{ <<"alg">> => <<"HS256">>
% , <<"typ">> => <<"JWT">> generate_jws('hmac-based', Payload, Secret) ->
% }, JWK = jose_jwk:from_oct(Secret),
% Signed = jose_jwt:sign(JWK, Header, Payload), Header = #{ <<"alg">> => <<"HS256">>
% {_, JWS} = jose_jws:compact(Signed), , <<"typ">> => <<"JWT">>
% JWS; },
% generate_jws('public-key', Payload, PrivateKey) -> Signed = jose_jwt:sign(JWK, Header, Payload),
% JWK = jose_jwk:from_pem_file(PrivateKey), {_, JWS} = jose_jws:compact(Signed),
% Header = #{ <<"alg">> => <<"RS256">> JWS;
% , <<"typ">> => <<"JWT">> generate_jws('public-key', Payload, PrivateKey) ->
% }, JWK = jose_jwk:from_pem_file(PrivateKey),
% Signed = jose_jwt:sign(JWK, Header, Payload), Header = #{ <<"alg">> => <<"RS256">>
% {_, JWS} = jose_jws:compact(Signed), , <<"typ">> => <<"JWT">>
% JWS. },
Signed = jose_jwt:sign(JWK, Header, Payload),
{_, JWS} = jose_jws:compact(Signed),
JWS.

View File

@ -68,6 +68,8 @@
-define(CONF_KEY_PATH, [authorization, sources]). -define(CONF_KEY_PATH, [authorization, sources]).
-define(RE_PLACEHOLDER, "\\$\\{[a-z0-9\\-]+\\}").
-define(USERNAME_RULES_EXAMPLE, #{username => user1, -define(USERNAME_RULES_EXAMPLE, #{username => user1,
rules => [ #{topic => <<"test/toopic/1">>, rules => [ #{topic => <<"test/toopic/1">>,
permission => <<"allow">>, permission => <<"allow">>,

View File

@ -38,10 +38,10 @@ description() ->
parse_query(undefined) -> parse_query(undefined) ->
undefined; undefined;
parse_query(Sql) -> parse_query(Sql) ->
case re:run(Sql, "'%[ucCad]'", [global, {capture, all, list}]) of case re:run(Sql, ?RE_PLACEHOLDER, [global, {capture, all, list}]) of
{match, Variables} -> {match, Variables} ->
Params = [Var || [Var] <- Variables], Params = [Var || [Var] <- Variables],
{re:replace(Sql, "'%[ucCad]'", "?", [global, {return, list}]), Params}; {re:replace(Sql, ?RE_PLACEHOLDER, "?", [global, {return, list}]), Params};
nomatch -> nomatch ->
{Sql, []} {Sql, []}
end. end.

View File

@ -38,7 +38,7 @@ description() ->
parse_query(undefined) -> parse_query(undefined) ->
undefined; undefined;
parse_query(Sql) -> parse_query(Sql) ->
case re:run(Sql, "'%[ucCad]'", [global, {capture, all, list}]) of case re:run(Sql, ?RE_PLACEHOLDER, [global, {capture, all, list}]) of
{match, Variables} -> {match, Variables} ->
Params = [Var || [Var] <- Variables], Params = [Var || [Var] <- Variables],
Vars = ["$" ++ integer_to_list(I) || I <- lists:seq(1, length(Params))], Vars = ["$" ++ integer_to_list(I) || I <- lists:seq(1, length(Params))],

View File

@ -169,8 +169,11 @@ health_check(PoolName) ->
case ecpool_worker:client(Worker) of case ecpool_worker:client(Worker) of
{ok, Conn} -> {ok, Conn} ->
%% we don't care if this returns something or not, we just to test the connection %% we don't care if this returns something or not, we just to test the connection
Res = mongo_api:find_one(Conn, <<"foo">>, {}, #{}), try mongo_api:find_one(Conn, <<"foo">>, {}, #{}) of
Res == undefined orelse is_map(Res); _ -> true
catch
_Class:_Error -> false
end;
_ -> false _ -> false
end end
end || {_WorkerName, Worker} <- ecpool:workers(PoolName)], end || {_WorkerName, Worker} <- ecpool:workers(PoolName)],

View File

@ -31,7 +31,8 @@
-define(TO_COMPONENTS_PARAM(_M_, _F_), iolist_to_binary([<<"#/components/parameters/">>, -define(TO_COMPONENTS_PARAM(_M_, _F_), iolist_to_binary([<<"#/components/parameters/">>,
?TO_REF(namespace(_M_), _F_)])). ?TO_REF(namespace(_M_), _F_)])).
-define(MAX_ROW_LIMIT, 100). -define(MAX_ROW_LIMIT, 1000).
-define(DEFAULT_ROW, 100).
-type(request() :: #{bindings => map(), query_string => map(), body => map()}). -type(request() :: #{bindings => map(), query_string => map(), body => map()}).
-type(request_meta() :: #{module => module(), path => string(), method => atom()}). -type(request_meta() :: #{module => module(), path => string(), method => atom()}).
@ -80,7 +81,7 @@ fields(page) ->
fields(limit) -> fields(limit) ->
Desc = iolist_to_binary([<<"Results per page(max ">>, Desc = iolist_to_binary([<<"Results per page(max ">>,
integer_to_binary(?MAX_ROW_LIMIT), <<")">>]), integer_to_binary(?MAX_ROW_LIMIT), <<")">>]),
Meta = #{in => query, desc => Desc, default => ?MAX_ROW_LIMIT, example => 50}, Meta = #{in => query, desc => Desc, default => ?DEFAULT_ROW, example => 50},
[{limit, hoconsc:mk(range(1, ?MAX_ROW_LIMIT), Meta)}]. [{limit, hoconsc:mk(range(1, ?MAX_ROW_LIMIT), Meta)}].
-spec(schema_with_example(hocon_schema:type(), term()) -> hocon_schema:field_schema_map()). -spec(schema_with_example(hocon_schema:type(), term()) -> hocon_schema:field_schema_map()).
@ -123,10 +124,10 @@ translate_req(Request, #{module := Module, path := Path, method := Method}, Chec
{Bindings, QueryStr} = check_parameters(Request, Params, Module), {Bindings, QueryStr} = check_parameters(Request, Params, Module),
NewBody = check_request_body(Request, Body, Module, CheckFun, hoconsc:is_schema(Body)), NewBody = check_request_body(Request, Body, Module, CheckFun, hoconsc:is_schema(Body)),
{ok, Request#{bindings => Bindings, query_string => QueryStr, body => NewBody}} {ok, Request#{bindings => Bindings, query_string => QueryStr, body => NewBody}}
catch throw:Error -> catch throw:{_, ValidErrors} ->
{_, [{validation_error, ValidErr}]} = Error, Msg = [io_lib:format("~ts : ~p", [Key, Reason]) ||
#{path := Key, reason := Reason} = ValidErr, {validation_error, #{path := Key, reason := Reason}} <- ValidErrors],
{400, 'BAD_REQUEST', iolist_to_binary(io_lib:format("~ts : ~p", [Key, Reason]))} {400, 'BAD_REQUEST', iolist_to_binary(string:join(Msg, ","))}
end. end.
check_and_translate(Schema, Map, Opts) -> check_and_translate(Schema, Map, Opts) ->

View File

@ -71,8 +71,10 @@ t_public_ref(_Config) ->
{emqx_dashboard_swagger, page, parameter} {emqx_dashboard_swagger, page, parameter}
], Refs), ], Refs),
ExpectRefs = [ ExpectRefs = [
#{<<"public.limit">> => #{description => <<"Results per page(max 100)">>, example => 50,in => query,name => limit, #{<<"public.limit">> => #{description => <<"Results per page(max 1000)">>,
schema => #{default => 100,example => 1,maximum => 100, minimum => 1,type => integer}}}, example => 50,in => query,name => limit,
schema => #{default => 100,example => 1,maximum => 1000,
minimum => 1,type => integer}}},
#{<<"public.page">> => #{description => <<"Page number of the results to fetch.">>, #{<<"public.page">> => #{description => <<"Page number of the results to fetch.">>,
example => 1,in => query,name => page, example => 1,in => query,name => page,
schema => #{default => 1,example => 100,type => integer}}}], schema => #{default => 1,example => 100,type => integer}}}],
@ -176,7 +178,8 @@ t_in_mix_trans(_Config) ->
Expect = {ok, Expect = {ok,
#{body => #{}, #{body => #{},
bindings => #{state => 720}, bindings => #{state => 720},
query_string => #{<<"filter">> => created,<<"is_admin">> => true, <<"per_page">> => 5,<<"timeout">> => 34}}}, query_string => #{<<"filter">> => created,<<"is_admin">> => true,
<<"per_page">> => 5,<<"timeout">> => 34}}},
?assertEqual(Expect, trans_parameters(Path, Bindings, Query)), ?assertEqual(Expect, trans_parameters(Path, Bindings, Query)),
ok. ok.
@ -268,7 +271,10 @@ schema("/test/in/:filter") ->
parameters => [ parameters => [
{filter, {filter,
mk(hoconsc:enum([assigned, created, mentioned, all]), mk(hoconsc:enum([assigned, created, mentioned, all]),
#{in => path, desc => <<"Indicates which sorts of issues to return">>, example => "all"})} #{in => path,
desc => <<"Indicates which sorts of issues to return">>,
example => "all"
})}
], ],
responses => #{200 => <<"ok">>} responses => #{200 => <<"ok">>}
} }
@ -323,9 +329,11 @@ schema("/test/in/mix/:state") ->
deprecated => true, deprecated => true,
parameters => [ parameters => [
{filter, hoconsc:mk(hoconsc:enum([assigned, created, mentioned, all]), {filter, hoconsc:mk(hoconsc:enum([assigned, created, mentioned, all]),
#{in => query, desc => <<"Indicates which sorts of issues to return">>, example => "all"})}, #{in => query, desc => <<"Indicates which sorts of issues to return">>,
example => "all"})},
{state, mk(emqx_schema:duration_s(), {state, mk(emqx_schema:duration_s(),
#{in => path, required => true, example => "12m", desc => <<"Indicates the state of the issues to return.">>})}, #{in => path, required => true, example => "12m",
desc => <<"Indicates the state of the issues to return.">>})},
{per_page, mk(range(1, 50), {per_page, mk(range(1, 50),
#{in => query, required => false, example => 10, default => 5})}, #{in => query, required => false, example => 10, default => 5})},
{is_admin, mk(boolean(), #{in => query})}, {is_admin, mk(boolean(), #{in => query})},

View File

@ -47,9 +47,14 @@ t_object(_Config) ->
#{<<"schema">> => #{<<"schema">> =>
#{required => [<<"timeout">>, <<"per_page">>], #{required => [<<"timeout">>, <<"per_page">>],
<<"properties">> =>[ <<"properties">> =>[
{<<"per_page">>, #{description => <<"good per page desc">>, example => 1, maximum => 100, minimum => 1, type => integer}}, {<<"per_page">>, #{description => <<"good per page desc">>,
{<<"timeout">>, #{default => 5, <<"oneOf">> => [#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}}, example => 1, maximum => 100, minimum => 1, type => integer}},
{<<"inner_ref">>, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}], {<<"timeout">>, #{default => 5, <<"oneOf">> =>
[#{example => <<"1h">>, type => string},
#{enum => [infinity], type => string}]}},
{<<"inner_ref">>,
#{<<"$ref">> =>
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}],
<<"type">> => object}}}}, <<"type">> => object}}}},
responses => #{<<"200">> => #{description => <<"ok">>}}}}, responses => #{<<"200">> => #{description => <<"ok">>}}}},
Refs = [{?MODULE, good_ref}], Refs = [{?MODULE, good_ref}],
@ -63,14 +68,20 @@ t_nest_object(_Config) ->
#{<<"schema">> => #{<<"schema">> =>
#{required => [<<"timeout">>], #{required => [<<"timeout">>],
<<"properties">> => <<"properties">> =>
[{<<"per_page">>, #{description => <<"good per page desc">>, example => 1, maximum => 100, minimum => 1, type => integer}}, [{<<"per_page">>, #{description => <<"good per page desc">>,
example => 1, maximum => 100, minimum => 1, type => integer}},
{<<"timeout">>, #{default => 5, <<"oneOf">> => {<<"timeout">>, #{default => 5, <<"oneOf">> =>
[#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}}, [#{example => <<"1h">>, type => string},
#{enum => [infinity], type => string}]}},
{<<"nest_object">>, {<<"nest_object">>,
#{<<"properties">> => #{<<"properties">> =>
[{<<"good_nest_1">>, #{example => 100, type => integer}}, [{<<"good_nest_1">>, #{example => 100, type => integer}},
{<<"good_nest_2">>, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}],<<"type">> => object}}, {<<"good_nest_2">>, #{<<"$ref">> =>
{<<"inner_ref">>, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}], <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}],
<<"type">> => object}},
{<<"inner_ref">>,
#{<<"$ref">> =>
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}],
<<"type">> => object}}}}, <<"type">> => object}}}},
responses => #{<<"200">> => #{description => <<"ok">>}}}}, responses => #{<<"200">> => #{description => <<"ok">>}}}},
Refs = [{?MODULE, good_ref}], Refs = [{?MODULE, good_ref}],
@ -81,7 +92,8 @@ t_local_ref(_Config) ->
Spec = #{ Spec = #{
post => #{parameters => [], post => #{parameters => [],
requestBody => #{<<"content">> => #{<<"application/json">> => requestBody => #{<<"content">> => #{<<"application/json">> =>
#{<<"schema">> => #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}}}, #{<<"schema">> => #{<<"$ref">> =>
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}}},
responses => #{<<"200">> => #{description => <<"ok">>}}}}, responses => #{<<"200">> => #{description => <<"ok">>}}}},
Refs = [{?MODULE, good_ref}], Refs = [{?MODULE, good_ref}],
validate("/ref/local", Spec, Refs), validate("/ref/local", Spec, Refs),
@ -91,17 +103,22 @@ t_remote_ref(_Config) ->
Spec = #{ Spec = #{
post => #{parameters => [], post => #{parameters => [],
requestBody => #{<<"content">> => #{<<"application/json">> => requestBody => #{<<"content">> => #{<<"application/json">> =>
#{<<"schema">> => #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref2">>}}}}, #{<<"schema">> => #{<<"$ref">> =>
<<"#/components/schemas/emqx_swagger_remote_schema.ref2">>}}}},
responses => #{<<"200">> => #{description => <<"ok">>}}}}, responses => #{<<"200">> => #{description => <<"ok">>}}}},
Refs = [{emqx_swagger_remote_schema, "ref2"}], Refs = [{emqx_swagger_remote_schema, "ref2"}],
{_, Components} = validate("/ref/remote", Spec, Refs), {_, Components} = validate("/ref/remote", Spec, Refs),
ExpectComponents = [ ExpectComponents = [
#{<<"emqx_swagger_remote_schema.ref2">> => #{<<"properties">> => [ #{<<"emqx_swagger_remote_schema.ref2">> => #{<<"properties">> => [
{<<"page">>, #{description => <<"good page">>,example => 1, maximum => 100,minimum => 1,type => integer}}, {<<"page">>, #{description => <<"good page">>,example => 1,
{<<"another_ref">>, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref3">>}}], <<"type">> => object}}, maximum => 100,minimum => 1,type => integer}},
{<<"another_ref">>, #{<<"$ref">> =>
<<"#/components/schemas/emqx_swagger_remote_schema.ref3">>}}], <<"type">> => object}},
#{<<"emqx_swagger_remote_schema.ref3">> => #{<<"properties">> => [ #{<<"emqx_swagger_remote_schema.ref3">> => #{<<"properties">> => [
{<<"ip">>, #{description => <<"IP:Port">>, example => <<"127.0.0.1:80">>,type => string}}, {<<"ip">>, #{description => <<"IP:Port">>,
{<<"version">>, #{description => <<"a good version">>, example => <<"1.0.0">>,type => string}}], example => <<"127.0.0.1:80">>,type => string}},
{<<"version">>, #{description => <<"a good version">>,
example => <<"1.0.0">>,type => string}}],
<<"type">> => object}}], <<"type">> => object}}],
?assertEqual(ExpectComponents, Components), ?assertEqual(ExpectComponents, Components),
ok. ok.
@ -110,18 +127,22 @@ t_nest_ref(_Config) ->
Spec = #{ Spec = #{
post => #{parameters => [], post => #{parameters => [],
requestBody => #{<<"content">> => #{<<"application/json">> => requestBody => #{<<"content">> => #{<<"application/json">> =>
#{<<"schema">> => #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_requestBody_SUITE.nest_ref">>}}}}, #{<<"schema">> => #{<<"$ref">> =>
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.nest_ref">>}}}},
responses => #{<<"200">> => #{description => <<"ok">>}}}}, responses => #{<<"200">> => #{description => <<"ok">>}}}},
Refs = [{?MODULE, nest_ref}], Refs = [{?MODULE, nest_ref}],
ExpectComponents = lists:sort([ ExpectComponents = lists:sort([
#{<<"emqx_swagger_requestBody_SUITE.nest_ref">> => #{<<"properties">> => [ #{<<"emqx_swagger_requestBody_SUITE.nest_ref">> => #{<<"properties">> => [
{<<"env">>, #{enum => [test,dev,prod],type => string}}, {<<"env">>, #{enum => [test,dev,prod],type => string}},
{<<"another_ref">>, #{description => <<"nest ref">>, <<"$ref">> => <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}], {<<"another_ref">>, #{description => <<"nest ref">>,
<<"$ref">> => <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}}],
<<"type">> => object}}, <<"type">> => object}},
#{<<"emqx_swagger_requestBody_SUITE.good_ref">> => #{<<"properties">> => [ #{<<"emqx_swagger_requestBody_SUITE.good_ref">> => #{<<"properties">> => [
{<<"webhook-host">>, #{default => <<"127.0.0.1:80">>, example => <<"127.0.0.1:80">>,type => string}}, {<<"webhook-host">>, #{default => <<"127.0.0.1:80">>,
example => <<"127.0.0.1:80">>,type => string}},
{<<"log_dir">>, #{example => <<"var/log/emqx">>,type => string}}, {<<"log_dir">>, #{example => <<"var/log/emqx">>,type => string}},
{<<"tag">>, #{description => <<"tag">>, example => <<"binary-example">>,type => string}}], {<<"tag">>, #{description => <<"tag">>,
example => <<"binary-example">>,type => string}}],
<<"type">> => object}}]), <<"type">> => object}}]),
{_, Components} = validate("/ref/nest/ref", Spec, Refs), {_, Components} = validate("/ref/nest/ref", Spec, Refs),
?assertEqual(ExpectComponents, Components), ?assertEqual(ExpectComponents, Components),
@ -153,9 +174,14 @@ t_ref_array_with_key(_Config) ->
#{<<"schema">> => #{required => [<<"timeout">>], #{<<"schema">> => #{required => [<<"timeout">>],
<<"type">> => object, <<"properties">> => <<"type">> => object, <<"properties">> =>
[ [
{<<"per_page">>, #{description => <<"good per page desc">>, example => 1, maximum => 100, minimum => 1, type => integer}}, {<<"per_page">>, #{description => <<"good per page desc">>,
{<<"timeout">>, #{default => 5, <<"oneOf">> => [#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}}, example => 1, maximum => 100, minimum => 1, type => integer}},
{<<"array_refs">>, #{items => #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}, type => array}} {<<"timeout">>, #{default => 5, <<"oneOf">> =>
[#{example => <<"1h">>, type => string},
#{enum => [infinity], type => string}]}},
{<<"array_refs">>, #{items => #{<<"$ref">> =>
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>},
type => array}}
]}}}}, ]}}}},
responses => #{<<"200">> => #{description => <<"ok">>}}}}, responses => #{<<"200">> => #{description => <<"ok">>}}}},
Refs = [{?MODULE, good_ref}], Refs = [{?MODULE, good_ref}],
@ -166,7 +192,8 @@ t_ref_array_without_key(_Config) ->
Spec = #{ Spec = #{
post => #{parameters => [], post => #{parameters => [],
requestBody => #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => requestBody => #{<<"content">> => #{<<"application/json">> => #{<<"schema">> =>
#{items => #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}, type => array}}}}, #{items => #{<<"$ref">> =>
<<"#/components/schemas/emqx_swagger_requestBody_SUITE.good_ref">>}, type => array}}}},
responses => #{<<"200">> => #{description => <<"ok">>}}}}, responses => #{<<"200">> => #{description => <<"ok">>}}}},
Refs = [{?MODULE, good_ref}], Refs = [{?MODULE, good_ref}],
validate("/ref/array/without/key", Spec, Refs), validate("/ref/array/without/key", Spec, Refs),
@ -190,7 +217,8 @@ t_api_spec(_Config) ->
{ok, #{body := #{<<"timeout">> := <<"infinity">>}}}, {ok, #{body := #{<<"timeout">> := <<"infinity">>}}},
trans_requestBody(Path, Body, Filter0)), trans_requestBody(Path, Body, Filter0)),
{Spec1, _} = emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true, translate_body => true}), {Spec1, _} = emqx_dashboard_swagger:spec(?MODULE,
#{check_schema => true, translate_body => true}),
Filter1 = filter(Spec1, Path), Filter1 = filter(Spec1, Path),
?assertMatch( ?assertMatch(
{ok, #{body := #{<<"timeout">> := infinity}}}, {ok, #{body := #{<<"timeout">> := infinity}}},
@ -237,7 +265,8 @@ t_object_notrans(_Config) ->
<<"tag">> => <<"god_tag">> <<"tag">> => <<"god_tag">>
} }
}, },
{ok, #{body := ActualBody}} = trans_requestBody(Path, Body, fun emqx_dashboard_swagger:filter_check_request/2), {ok, #{body := ActualBody}} = trans_requestBody(Path, Body,
fun emqx_dashboard_swagger:filter_check_request/2),
?assertEqual(Body, ActualBody), ?assertEqual(Body, ActualBody),
ok. ok.
@ -444,7 +473,8 @@ filter(ApiSpec, Path) ->
Filter. Filter.
trans_requestBody(Path, Body) -> trans_requestBody(Path, Body) ->
trans_requestBody(Path, Body, fun emqx_dashboard_swagger:filter_check_request_and_translate_body/2). trans_requestBody(Path, Body,
fun emqx_dashboard_swagger:filter_check_request_and_translate_body/2).
trans_requestBody(Path, Body, Filter) -> trans_requestBody(Path, Body, Filter) ->
Meta = #{module => ?MODULE, method => post, path => Path}, Meta = #{module => ?MODULE, method => post, path => Path},
@ -453,7 +483,8 @@ trans_requestBody(Path, Body, Filter) ->
api_spec() -> emqx_dashboard_swagger:spec(?MODULE). api_spec() -> emqx_dashboard_swagger:spec(?MODULE).
paths() -> paths() ->
["/object", "/nest/object", "/ref/local", "/ref/nest/ref", "/ref/array/with/key", "/ref/array/without/key"]. ["/object", "/nest/object", "/ref/local", "/ref/nest/ref",
"/ref/array/with/key", "/ref/array/without/key"].
schema("/object") -> schema("/object") ->
to_schema([ to_schema([

View File

@ -40,9 +40,12 @@ t_object(_config) ->
#{<<"content">> => #{<<"application/json">> => #{<<"content">> => #{<<"application/json">> =>
#{<<"schema">> => #{required => [<<"timeout">>, <<"per_page">>], #{<<"schema">> => #{required => [<<"timeout">>, <<"per_page">>],
<<"properties">> => [ <<"properties">> => [
{<<"per_page">>, #{description => <<"good per page desc">>, example => 1, maximum => 100, minimum => 1, type => integer}}, {<<"per_page">>, #{description => <<"good per page desc">>,
{<<"timeout">>, #{default => 5, <<"oneOf">> => [#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}}, example => 1, maximum => 100, minimum => 1, type => integer}},
{<<"inner_ref">>, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}}], {<<"timeout">>, #{default => 5, <<"oneOf">> =>
[#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}},
{<<"inner_ref">>, #{<<"$ref">> =>
<<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}}],
<<"type">> => object}}}}, <<"type">> => object}}}},
ExpectRefs = [{?MODULE, good_ref}], ExpectRefs = [{?MODULE, good_ref}],
validate(Path, Object, ExpectRefs), validate(Path, Object, ExpectRefs),
@ -80,14 +83,17 @@ t_nest_object(_Config) ->
Object = Object =
#{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{<<"content">> => #{<<"application/json">> => #{<<"schema">> =>
#{required => [<<"timeout">>], <<"type">> => object, <<"properties">> => [ #{required => [<<"timeout">>], <<"type">> => object, <<"properties">> => [
{<<"per_page">>, #{description => <<"good per page desc">>, example => 1, maximum => 100, minimum => 1, type => integer}}, {<<"per_page">>, #{description => <<"good per page desc">>, example => 1,
maximum => 100, minimum => 1, type => integer}},
{<<"timeout">>, #{default => 5, <<"oneOf">> => {<<"timeout">>, #{default => 5, <<"oneOf">> =>
[#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}}, [#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}},
{<<"nest_object">>, #{<<"type">> => object, <<"properties">> => [ {<<"nest_object">>, #{<<"type">> => object, <<"properties">> => [
{<<"good_nest_1">>, #{example => 100, type => integer}}, {<<"good_nest_1">>, #{example => 100, type => integer}},
{<<"good_nest_2">>, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>} {<<"good_nest_2">>, #{<<"$ref">> =>
<<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}
}]}}, }]}},
{<<"inner_ref">>, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}}] {<<"inner_ref">>, #{<<"$ref">> =>
<<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}}]
}}}}, }}}},
ExpectRefs = [{?MODULE, good_ref}], ExpectRefs = [{?MODULE, good_ref}],
validate(Path, Object, ExpectRefs), validate(Path, Object, ExpectRefs),
@ -138,7 +144,8 @@ t_bad_ref(_Config) ->
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> =>
#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.bad_ref">>}}}}, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.bad_ref">>}}}},
ExpectRefs = [{?MODULE, bad_ref}], ExpectRefs = [{?MODULE, bad_ref}],
?assertThrow({error, #{module := ?MODULE, msg := <<"Object only supports not empty proplists">>}}, ?assertThrow({error, #{module := ?MODULE,
msg := <<"Object only supports not empty proplists">>}},
validate(Path, Object, ExpectRefs)), validate(Path, Object, ExpectRefs)),
ok. ok.
@ -158,7 +165,8 @@ t_nest_ref(_Config) ->
t_complicated_type(_Config) -> t_complicated_type(_Config) ->
Path = "/ref/complicated_type", Path = "/ref/complicated_type",
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{<<"properties">> => Object = #{<<"content">> => #{<<"application/json">> =>
#{<<"schema">> => #{<<"properties">> =>
[ [
{<<"no_neg_integer">>, #{example => 100, minimum => 1, type => integer}}, {<<"no_neg_integer">>, #{example => 100, minimum => 1, type => integer}},
{<<"url">>, #{example => <<"http://127.0.0.1">>, type => string}}, {<<"url">>, #{example => <<"http://127.0.0.1">>, type => string}},
@ -176,7 +184,8 @@ t_complicated_type(_Config) ->
{<<"comma_separated_list">>, #{example => <<"item1,item2">>, type => string}}, {<<"comma_separated_list">>, #{example => <<"item1,item2">>, type => string}},
{<<"comma_separated_atoms">>, #{example => <<"item1,item2">>, type => string}}, {<<"comma_separated_atoms">>, #{example => <<"item1,item2">>, type => string}},
{<<"log_level">>, {<<"log_level">>,
#{enum => [debug, info, notice, warning, error, critical, alert, emergency, all], type => string}}, #{enum => [debug, info, notice, warning, error, critical, alert, emergency, all],
type => string}},
{<<"fix_integer">>, #{default => 100, enum => [100], example => 100,type => integer}} {<<"fix_integer">>, #{default => 100, enum => [100], example => 100,type => integer}}
], ],
<<"type">> => object}}}}, <<"type">> => object}}}},
@ -192,15 +201,20 @@ t_ref_array_with_key(_Config) ->
Path = "/ref/array/with/key", Path = "/ref/array/with/key",
Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{ Object = #{<<"content">> => #{<<"application/json">> => #{<<"schema">> => #{
required => [<<"timeout">>], <<"type">> => object, <<"properties">> => [ required => [<<"timeout">>], <<"type">> => object, <<"properties">> => [
{<<"per_page">>, #{description => <<"good per page desc">>, example => 1, maximum => 100, minimum => 1, type => integer}}, {<<"per_page">>, #{description => <<"good per page desc">>,
example => 1, maximum => 100, minimum => 1, type => integer}},
{<<"timeout">>, #{default => 5, <<"oneOf">> => {<<"timeout">>, #{default => 5, <<"oneOf">> =>
[#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}}, [#{example => <<"1h">>, type => string}, #{enum => [infinity], type => string}]}},
{<<"assert">>, #{description => <<"money">>, example => 3.14159, type => number}}, {<<"assert">>, #{description => <<"money">>, example => 3.14159, type => number}},
{<<"number_ex">>, #{description => <<"number example">>, example => 42, type => number}}, {<<"number_ex">>, #{description => <<"number example">>,
{<<"percent_ex">>, #{description => <<"percent example">>, example => <<"12%">>, type => number}}, example => 42, type => number}},
{<<"duration_ms_ex">>, #{description => <<"duration ms example">>, example => <<"32s">>, type => string}}, {<<"percent_ex">>, #{description => <<"percent example">>,
example => <<"12%">>, type => number}},
{<<"duration_ms_ex">>, #{description => <<"duration ms example">>,
example => <<"32s">>, type => string}},
{<<"atom_ex">>, #{description => <<"atom ex">>, example => atom, type => string}}, {<<"atom_ex">>, #{description => <<"atom ex">>, example => atom, type => string}},
{<<"array_refs">>, #{items => #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}, type => array}} {<<"array_refs">>, #{items => #{<<"$ref">> =>
<<"#/components/schemas/emqx_swagger_response_SUITE.good_ref">>}, type => array}}
]} ]}
}}}, }}},
ExpectRefs = [{?MODULE, good_ref}], ExpectRefs = [{?MODULE, good_ref}],
@ -227,25 +241,34 @@ t_hocon_schema_function(_Config) ->
}}, }},
#{<<"emqx_swagger_remote_schema.ref2">> => #{<<"type">> => object, #{<<"emqx_swagger_remote_schema.ref2">> => #{<<"type">> => object,
<<"properties">> => [ <<"properties">> => [
{<<"page">>, #{description => <<"good page">>, example => 1, maximum => 100, minimum => 1, type => integer}}, {<<"page">>, #{description => <<"good page">>,
{<<"another_ref">>, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref3">>}} example => 1, maximum => 100, minimum => 1, type => integer}},
{<<"another_ref">>, #{<<"$ref">> =>
<<"#/components/schemas/emqx_swagger_remote_schema.ref3">>}}
] ]
}}, }},
#{<<"emqx_swagger_remote_schema.ref3">> => #{<<"type">> => object, #{<<"emqx_swagger_remote_schema.ref3">> => #{<<"type">> => object,
<<"properties">> => [ <<"properties">> => [
{<<"ip">>, #{description => <<"IP:Port">>, example => <<"127.0.0.1:80">>, type => string}}, {<<"ip">>, #{description => <<"IP:Port">>,
{<<"version">>, #{description => <<"a good version">>, example => <<"1.0.0">>, type => string}}] example => <<"127.0.0.1:80">>, type => string}},
{<<"version">>, #{description => <<"a good version">>,
example => <<"1.0.0">>, type => string}}]
}}, }},
#{<<"emqx_swagger_remote_schema.root">> => #{required => [<<"default_password">>, <<"default_username">>], #{<<"emqx_swagger_remote_schema.root">> =>
#{required => [<<"default_password">>, <<"default_username">>],
<<"properties">> => [{<<"listeners">>, #{items => <<"properties">> => [{<<"listeners">>, #{items =>
#{<<"oneOf">> => #{<<"oneOf">> =>
[#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref2">>}, [#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref2">>},
#{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref1">>}]}, type => array}}, #{<<"$ref">> => <<"#/components/schemas/emqx_swagger_remote_schema.ref1">>}]},
type => array}},
{<<"default_username">>, {<<"default_username">>,
#{default => <<"admin">>, example => <<"string-example">>, type => string}}, #{default => <<"admin">>, example => <<"string-example">>, type => string}},
{<<"default_password">>, #{default => <<"public">>, example => <<"string-example">>, type => string}}, {<<"default_password">>,
{<<"sample_interval">>, #{default => <<"10s">>, example => <<"1h">>, type => string}}, #{default => <<"public">>, example => <<"string-example">>, type => string}},
{<<"token_expired_time">>, #{default => <<"30m">>, example => <<"12m">>, type => string}}], {<<"sample_interval">>,
#{default => <<"10s">>, example => <<"1h">>, type => string}},
{<<"token_expired_time">>,
#{default => <<"30m">>, example => <<"12m">>, type => string}}],
<<"type">> => object}}], <<"type">> => object}}],
ExpectRefs = [{emqx_swagger_remote_schema, "root"}], ExpectRefs = [{emqx_swagger_remote_schema, "root"}],
{_, Components} = validate(Path, Object, ExpectRefs), {_, Components} = validate(Path, Object, ExpectRefs),

View File

@ -89,8 +89,8 @@ start_listener(GwName, Ctx, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) of case start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) of
{ok, Pid} -> {ok, Pid} ->
?ULOG("Gateway ~ts:~ts:~ts on ~ts started.~n", console_print("Gateway ~ts:~ts:~ts on ~ts started.~n",
[GwName, Type, LisName, ListenOnStr]), [GwName, Type, LisName, ListenOnStr]),
Pid; Pid;
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n", ?ELOG("Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n",
@ -118,8 +118,9 @@ stop_listener(GwName, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
StopRet = stop_listener(GwName, Type, LisName, ListenOn, SocketOpts, Cfg), StopRet = stop_listener(GwName, Type, LisName, ListenOn, SocketOpts, Cfg),
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case StopRet of case StopRet of
ok -> ?ULOG("Gateway ~ts:~ts:~ts on ~ts stopped.~n", ok ->
[GwName, Type, LisName, ListenOnStr]); console_print("Gateway ~ts:~ts:~ts on ~ts stopped.~n",
[GwName, Type, LisName, ListenOnStr]);
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n", ?ELOG("Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n",
[GwName, Type, LisName, ListenOnStr, Reason]) [GwName, Type, LisName, ListenOnStr, Reason])
@ -129,3 +130,9 @@ stop_listener(GwName, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
stop_listener(GwName, Type, LisName, ListenOn, _SocketOpts, _Cfg) -> stop_listener(GwName, Type, LisName, ListenOn, _SocketOpts, _Cfg) ->
Name = emqx_gateway_utils:listener_id(GwName, Type, LisName), Name = emqx_gateway_utils:listener_id(GwName, Type, LisName),
esockd:close(Name, ListenOn). esockd:close(Name, ListenOn).
-ifndef(TEST).
console_print(Fmt, Args) -> ?ULOG(Fmt, Args).
-else.
console_print(_Fmt, _Args) -> ok.
-endif.

View File

@ -59,12 +59,18 @@ start_grpc_server(GwName, Options = #{bind := ListenOn}) ->
SslOpts -> SslOpts ->
[{ssl_options, SslOpts}] [{ssl_options, SslOpts}]
end, end,
_ = grpc:start_server(GwName, ListenOn, Services, SvrOptions), case grpc:start_server(GwName, ListenOn, Services, SvrOptions) of
?ULOG("Start ~ts gRPC server on ~p successfully.~n", [GwName, ListenOn]). {ok, _SvrPid} ->
console_print("Start ~ts gRPC server on ~p successfully.",
[GwName, ListenOn]);
{error, Reason} ->
?ELOG("Falied to start ~ts gRPC server on ~p, reason: ~p",
[GwName, ListenOn, Reason])
end.
stop_grpc_server(GwName) -> stop_grpc_server(GwName) ->
_ = grpc:stop_server(GwName), _ = grpc:stop_server(GwName),
?ULOG("Stop ~s gRPC server successfully.~n", [GwName]). console_print("Stop ~s gRPC server successfully.~n", [GwName]).
start_grpc_client_channel(_GwName, undefined) -> start_grpc_client_channel(_GwName, undefined) ->
undefined; undefined;
@ -157,8 +163,8 @@ start_listener(GwName, Ctx, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) of case start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) of
{ok, Pid} -> {ok, Pid} ->
?ULOG("Gateway ~ts:~ts:~ts on ~ts started.~n", console_print("Gateway ~ts:~ts:~ts on ~ts started.~n",
[GwName, Type, LisName, ListenOnStr]), [GwName, Type, LisName, ListenOnStr]),
Pid; Pid;
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n", ?ELOG("Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n",
@ -212,8 +218,9 @@ stop_listener(GwName, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
StopRet = stop_listener(GwName, Type, LisName, ListenOn, SocketOpts, Cfg), StopRet = stop_listener(GwName, Type, LisName, ListenOn, SocketOpts, Cfg),
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case StopRet of case StopRet of
ok -> ?ULOG("Gateway ~ts:~ts:~ts on ~ts stopped.~n", ok ->
[GwName, Type, LisName, ListenOnStr]); console_print("Gateway ~ts:~ts:~ts on ~ts stopped.~n",
[GwName, Type, LisName, ListenOnStr]);
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n", ?ELOG("Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n",
[GwName, Type, LisName, ListenOnStr, Reason]) [GwName, Type, LisName, ListenOnStr, Reason])
@ -223,3 +230,9 @@ stop_listener(GwName, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
stop_listener(GwName, Type, LisName, ListenOn, _SocketOpts, _Cfg) -> stop_listener(GwName, Type, LisName, ListenOn, _SocketOpts, _Cfg) ->
Name = emqx_gateway_utils:listener_id(GwName, Type, LisName), Name = emqx_gateway_utils:listener_id(GwName, Type, LisName),
esockd:close(Name, ListenOn). esockd:close(Name, ListenOn).
-ifndef(TEST).
console_print(Fmt, Args) -> ?ULOG(Fmt, Args).
-else.
console_print(_Fmt, _Args) -> ok.
-endif.

View File

@ -91,8 +91,8 @@ start_listener(GwName, Ctx, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) of case start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) of
{ok, Pid} -> {ok, Pid} ->
?ULOG("Gateway ~ts:~ts:~ts on ~ts started.~n", console_print("Gateway ~ts:~ts:~ts on ~ts started.~n",
[GwName, Type, LisName, ListenOnStr]), [GwName, Type, LisName, ListenOnStr]),
Pid; Pid;
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n", ?ELOG("Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n",
@ -131,8 +131,9 @@ stop_listener(GwName, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
StopRet = stop_listener(GwName, Type, LisName, ListenOn, SocketOpts, Cfg), StopRet = stop_listener(GwName, Type, LisName, ListenOn, SocketOpts, Cfg),
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case StopRet of case StopRet of
ok -> ?ULOG("Gateway ~ts:~ts:~ts on ~ts stopped.~n", ok ->
[GwName, Type, LisName, ListenOnStr]); console_print("Gateway ~ts:~ts:~ts on ~ts stopped.~n",
[GwName, Type, LisName, ListenOnStr]);
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n", ?ELOG("Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n",
[GwName, Type, LisName, ListenOnStr, Reason]) [GwName, Type, LisName, ListenOnStr, Reason])
@ -142,3 +143,9 @@ stop_listener(GwName, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
stop_listener(GwName, Type, LisName, ListenOn, _SocketOpts, _Cfg) -> stop_listener(GwName, Type, LisName, ListenOn, _SocketOpts, _Cfg) ->
Name = emqx_gateway_utils:listener_id(GwName, Type, LisName), Name = emqx_gateway_utils:listener_id(GwName, Type, LisName),
esockd:close(Name, ListenOn). esockd:close(Name, ListenOn).
-ifndef(TEST).
console_print(Fmt, Args) -> ?ULOG(Fmt, Args).
-else.
console_print(_Fmt, _Args) -> ok.
-endif.

View File

@ -108,8 +108,8 @@ start_listener(GwName, Ctx, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) of case start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) of
{ok, Pid} -> {ok, Pid} ->
?ULOG("Gateway ~ts:~ts:~ts on ~ts started.~n", console_print("Gateway ~ts:~ts:~ts on ~ts started.~n",
[GwName, Type, LisName, ListenOnStr]), [GwName, Type, LisName, ListenOnStr]),
Pid; Pid;
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n", ?ELOG("Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n",
@ -142,8 +142,9 @@ stop_listener(GwName, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
StopRet = stop_listener(GwName, Type, LisName, ListenOn, SocketOpts, Cfg), StopRet = stop_listener(GwName, Type, LisName, ListenOn, SocketOpts, Cfg),
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case StopRet of case StopRet of
ok -> ?ULOG("Gateway ~ts:~ts:~ts on ~ts stopped.~n", ok ->
[GwName, Type, LisName, ListenOnStr]); console_print("Gateway ~ts:~ts:~ts on ~ts stopped.~n",
[GwName, Type, LisName, ListenOnStr]);
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n", ?ELOG("Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n",
[GwName, Type, LisName, ListenOnStr, Reason]) [GwName, Type, LisName, ListenOnStr, Reason])
@ -153,3 +154,9 @@ stop_listener(GwName, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
stop_listener(GwName, Type, LisName, ListenOn, _SocketOpts, _Cfg) -> stop_listener(GwName, Type, LisName, ListenOn, _SocketOpts, _Cfg) ->
Name = emqx_gateway_utils:listener_id(GwName, Type, LisName), Name = emqx_gateway_utils:listener_id(GwName, Type, LisName),
esockd:close(Name, ListenOn). esockd:close(Name, ListenOn).
-ifndef(TEST).
console_print(Fmt, Args) -> ?ULOG(Fmt, Args).
-else.
console_print(_Fmt, _Args) -> ok.
-endif.

View File

@ -93,8 +93,8 @@ start_listener(GwName, Ctx, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) of case start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) of
{ok, Pid} -> {ok, Pid} ->
?ULOG("Gateway ~ts:~ts:~ts on ~ts started.~n", console_print("Gateway ~ts:~ts:~ts on ~ts started.~n",
[GwName, Type, LisName, ListenOnStr]), [GwName, Type, LisName, ListenOnStr]),
Pid; Pid;
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n", ?ELOG("Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n",
@ -127,8 +127,9 @@ stop_listener(GwName, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
StopRet = stop_listener(GwName, Type, LisName, ListenOn, SocketOpts, Cfg), StopRet = stop_listener(GwName, Type, LisName, ListenOn, SocketOpts, Cfg),
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case StopRet of case StopRet of
ok -> ?ULOG("Gateway ~ts:~ts:~ts on ~ts stopped.~n", ok ->
[GwName, Type, LisName, ListenOnStr]); console_print("Gateway ~ts:~ts:~ts on ~ts stopped.~n",
[GwName, Type, LisName, ListenOnStr]);
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n", ?ELOG("Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n",
[GwName, Type, LisName, ListenOnStr, Reason]) [GwName, Type, LisName, ListenOnStr, Reason])
@ -138,3 +139,9 @@ stop_listener(GwName, {Type, LisName, ListenOn, SocketOpts, Cfg}) ->
stop_listener(GwName, Type, LisName, ListenOn, _SocketOpts, _Cfg) -> stop_listener(GwName, Type, LisName, ListenOn, _SocketOpts, _Cfg) ->
Name = emqx_gateway_utils:listener_id(GwName, Type, LisName), Name = emqx_gateway_utils:listener_id(GwName, Type, LisName),
esockd:close(Name, ListenOn). esockd:close(Name, ListenOn).
-ifndef(TEST).
console_print(Fmt, Args) -> ?ULOG(Fmt, Args).
-else.
console_print(_Fmt, _Args) -> ok.
-endif.

View File

@ -40,7 +40,7 @@
, on_received_messages/2 , on_received_messages/2
]). ]).
-define(LOG(Fmt, Args), io:format(standard_error, Fmt, Args)). -define(LOG(Fmt, Args), ct:pal(Fmt, Args)).
-define(HTTP, #{grpc_opts => #{service_protos => [emqx_exproto_pb], -define(HTTP, #{grpc_opts => #{service_protos => [emqx_exproto_pb],
services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE}}, services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE}},
@ -188,13 +188,15 @@ on_received_messages(Stream, _Md) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_in(Conn, ?TYPE_CONNECT, #{<<"clientinfo">> := ClientInfo, <<"password">> := Password}) -> handle_in(Conn, ?TYPE_CONNECT, #{<<"clientinfo">> := ClientInfo, <<"password">> := Password}) ->
NClientInfo = maps:from_list([{binary_to_atom(K, utf8), V} || {K, V} <- maps:to_list(ClientInfo)]), NClientInfo = maps:from_list(
[{binary_to_atom(K, utf8), V}
|| {K, V} <- maps:to_list(ClientInfo)]),
case ?authenticate(#{conn => Conn, clientinfo => NClientInfo, password => Password}) of case ?authenticate(#{conn => Conn, clientinfo => NClientInfo, password => Password}) of
{ok, #{code := 'SUCCESS'}, _} -> {ok, #{code := 'SUCCESS'}, _} ->
case maps:get(keepalive, NClientInfo, 0) of case maps:get(keepalive, NClientInfo, 0) of
0 -> ok; 0 -> ok;
Intv -> Intv ->
io:format("Try call start_timer with ~ps", [Intv]), ?LOG("Try call start_timer with ~ps", [Intv]),
?start_timer(#{conn => Conn, type => 'KEEPALIVE', interval => Intv}) ?start_timer(#{conn => Conn, type => 'KEEPALIVE', interval => Intv})
end, end,
handle_out(Conn, ?TYPE_CONNACK, 0); handle_out(Conn, ?TYPE_CONNACK, 0);

View File

@ -36,7 +36,7 @@
-define(FLAG_RETAIN(X),X). -define(FLAG_RETAIN(X),X).
-define(FLAG_SESSION(X),X). -define(FLAG_SESSION(X),X).
-define(LOG(Format, Args), ct:print("TEST: " ++ Format, Args)). -define(LOG(Format, Args), ct:pal("TEST: " ++ Format, Args)).
-define(MAX_PRED_TOPIC_ID, 2). -define(MAX_PRED_TOPIC_ID, 2).
-define(PREDEF_TOPIC_ID1, 1). -define(PREDEF_TOPIC_ID1, 1).
@ -51,6 +51,8 @@
-define(CLIENTID, iolist_to_binary([atom_to_list(?FUNCTION_NAME), "-", -define(CLIENTID, iolist_to_binary([atom_to_list(?FUNCTION_NAME), "-",
integer_to_list(erlang:system_time())])). integer_to_list(erlang:system_time())])).
-elvis([{elvis_style, dont_repeat_yourself, disable}]).
-define(CONF_DEFAULT, <<" -define(CONF_DEFAULT, <<"
gateway.mqttsn { gateway.mqttsn {
gateway_id = 1 gateway_id = 1
@ -177,7 +179,7 @@ t_subscribe_case02(_) ->
Will = 0, Will = 0,
CleanSession = 0, CleanSession = 0,
MsgId = 1, MsgId = 1,
TopicId = ?PREDEF_TOPIC_ID1, %this TopicId is the predefined topic id corresponding to ?PREDEF_TOPIC_NAME1 TopicId = ?PREDEF_TOPIC_ID1,
ReturnCode = 0, ReturnCode = 0,
{ok, Socket} = gen_udp:open(0, [binary]), {ok, Socket} = gen_udp:open(0, [binary]),
@ -227,8 +229,14 @@ t_subscribe_case03(_) ->
?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)), ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
gen_udp:close(Socket). gen_udp:close(Socket).
%%In this case We use predefined topic name to register and subcribe, and expect to receive the corresponding predefined topic id but not a new generated topic id from broker. We design this case to illustrate %% In this case We use predefined topic name to register and subcribe,
%% emqx_sn_gateway's compatibility of dealing with predefined and normal topics. Once we give more restrictions to different topic id type, this case would be deleted or modified. %% and expect to receive the corresponding predefined topic id but not a new
%% generated topic id from broker. We design this case to illustrate
%% emqx_sn_gateway's compatibility of dealing with predefined and normal
%% topics.
%%
%% Once we give more restrictions to different topic id type, this case
%% would be deleted or modified.
t_subscribe_case04(_) -> t_subscribe_case04(_) ->
Dup = 0, Dup = 0,
QoS = 0, QoS = 0,
@ -236,7 +244,7 @@ t_subscribe_case04(_) ->
Will = 0, Will = 0,
CleanSession = 0, CleanSession = 0,
MsgId = 1, MsgId = 1,
TopicId = ?PREDEF_TOPIC_ID1, %this TopicId is the predefined topic id corresponding to ?PREDEF_TOPIC_NAME1 TopicId = ?PREDEF_TOPIC_ID1,
ReturnCode = 0, ReturnCode = 0,
{ok, Socket} = gen_udp:open(0, [binary]), {ok, Socket} = gen_udp:open(0, [binary]),
ClientId = ?CLIENTID, ClientId = ?CLIENTID,
@ -246,8 +254,12 @@ t_subscribe_case04(_) ->
send_register_msg(Socket, Topic1, MsgId), send_register_msg(Socket, Topic1, MsgId),
?assertEqual(<<7, ?SN_REGACK, TopicId:16, MsgId:16, 0:8>>, receive_response(Socket)), ?assertEqual(<<7, ?SN_REGACK, TopicId:16, MsgId:16, 0:8>>, receive_response(Socket)),
send_subscribe_msg_normal_topic(Socket, QoS, Topic1, MsgId), send_subscribe_msg_normal_topic(Socket, QoS, Topic1, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId:16, MsgId:16, ReturnCode>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId:16, MsgId:16, ReturnCode>>,
receive_response(Socket)
),
send_unsubscribe_msg_normal_topic(Socket, Topic1, MsgId), send_unsubscribe_msg_normal_topic(Socket, Topic1, MsgId),
?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)), ?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)),
@ -277,21 +289,36 @@ t_subscribe_case05(_) ->
?assertEqual(<<7, ?SN_REGACK, TopicId1:16, MsgId:16, 0:8>>, receive_response(Socket)), ?assertEqual(<<7, ?SN_REGACK, TopicId1:16, MsgId:16, 0:8>>, receive_response(Socket)),
send_subscribe_msg_normal_topic(Socket, QoS, <<"abcD">>, MsgId), send_subscribe_msg_normal_topic(Socket, QoS, <<"abcD">>, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId1:16, MsgId:16, ReturnCode>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId1:16, MsgId:16, ReturnCode>>,
receive_response(Socket)
),
send_subscribe_msg_normal_topic(Socket, QoS, <<"/sport/#">>, MsgId), send_subscribe_msg_normal_topic(Socket, QoS, <<"/sport/#">>, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId0:16, MsgId:16, ReturnCode>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId0:16, MsgId:16, ReturnCode>>,
receive_response(Socket)
),
send_subscribe_msg_normal_topic(Socket, QoS, <<"/a/+/water">>, MsgId), send_subscribe_msg_normal_topic(Socket, QoS, <<"/a/+/water">>, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId0:16, MsgId:16, ReturnCode>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId0:16, MsgId:16, ReturnCode>>,
receive_response(Socket)
),
send_subscribe_msg_normal_topic(Socket, QoS, <<"/Tom/Home">>, MsgId), send_subscribe_msg_normal_topic(Socket, QoS, <<"/Tom/Home">>, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?assertEqual(
?SN_NORMAL_TOPIC:2, TopicId2:16, MsgId:16, ReturnCode>>, <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
receive_response(Socket)), Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId2:16, MsgId:16, ReturnCode>>,
receive_response(Socket)
),
send_unsubscribe_msg_normal_topic(Socket, <<"abcD">>, MsgId), send_unsubscribe_msg_normal_topic(Socket, <<"abcD">>, MsgId),
?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)), ?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)),
@ -316,19 +343,37 @@ t_subscribe_case06(_) ->
?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
send_register_msg(Socket, <<"abc">>, MsgId), send_register_msg(Socket, <<"abc">>, MsgId),
?assertEqual(<<7, ?SN_REGACK, TopicId1:16, MsgId:16, 0:8>>, receive_response(Socket)), ?assertEqual(
<<7, ?SN_REGACK, TopicId1:16, MsgId:16, 0:8>>,
receive_response(Socket)
),
send_register_msg(Socket, <<"/blue/#">>, MsgId), send_register_msg(Socket, <<"/blue/#">>, MsgId),
?assertEqual(<<7, ?SN_REGACK, TopicId0:16, MsgId:16, ?SN_RC_NOT_SUPPORTED:8>>, receive_response(Socket)), ?assertEqual(
<<7, ?SN_REGACK, TopicId0:16,
MsgId:16, ?SN_RC_NOT_SUPPORTED:8>>,
receive_response(Socket)
),
send_register_msg(Socket, <<"/blue/+/white">>, MsgId), send_register_msg(Socket, <<"/blue/+/white">>, MsgId),
?assertEqual(<<7, ?SN_REGACK, TopicId0:16, MsgId:16, ?SN_RC_NOT_SUPPORTED:8>>, receive_response(Socket)), ?assertEqual(
<<7, ?SN_REGACK, TopicId0:16,
MsgId:16, ?SN_RC_NOT_SUPPORTED:8>>,
receive_response(Socket)
),
send_register_msg(Socket, <<"/$sys/rain">>, MsgId), send_register_msg(Socket, <<"/$sys/rain">>, MsgId),
?assertEqual(<<7, ?SN_REGACK, TopicId2:16, MsgId:16, 0:8>>, receive_response(Socket)), ?assertEqual(
<<7, ?SN_REGACK, TopicId2:16, MsgId:16, 0:8>>,
receive_response(Socket)
),
send_subscribe_msg_short_topic(Socket, QoS, <<"Q2">>, MsgId), send_subscribe_msg_short_topic(Socket, QoS, <<"Q2">>, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId0:16, MsgId:16, ReturnCode>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1,
CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId0:16, MsgId:16, ReturnCode>>,
receive_response(Socket)
),
send_unsubscribe_msg_normal_topic(Socket, <<"Q2">>, MsgId), send_unsubscribe_msg_normal_topic(Socket, <<"Q2">>, MsgId),
?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)), ?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)),
@ -352,8 +397,12 @@ t_subscribe_case07(_) ->
?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
send_subscribe_msg_predefined_topic(Socket, QoS, TopicId1, MsgId), send_subscribe_msg_predefined_topic(Socket, QoS, TopicId1, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, ?SN_INVALID_TOPIC_ID:16, MsgId:16, ?SN_RC_INVALID_TOPIC_ID>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
?SN_INVALID_TOPIC_ID:16, MsgId:16, ?SN_RC_INVALID_TOPIC_ID>>,
receive_response(Socket)
),
send_unsubscribe_msg_predefined_topic(Socket, TopicId2, MsgId), send_unsubscribe_msg_predefined_topic(Socket, TopicId2, MsgId),
?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)), ?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)),
@ -375,8 +424,12 @@ t_subscribe_case08(_) ->
?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
send_subscribe_msg_reserved_topic(Socket, QoS, TopicId2, MsgId), send_subscribe_msg_reserved_topic(Socket, QoS, TopicId2, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, ?SN_INVALID_TOPIC_ID:16, MsgId:16, ?SN_RC_NOT_SUPPORTED>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
?SN_INVALID_TOPIC_ID:16, MsgId:16, ?SN_RC_NOT_SUPPORTED>>,
receive_response(Socket)
),
send_disconnect_msg(Socket, undefined), send_disconnect_msg(Socket, undefined),
?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)), ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
@ -400,15 +453,21 @@ t_publish_negqos_case09(_) ->
send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId), send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
receive_response(Socket)
),
MsgId1 = 3, MsgId1 = 3,
Payload1 = <<20, 21, 22, 23>>, Payload1 = <<20, 21, 22, 23>>,
send_publish_msg_normal_topic(Socket, NegQoS, MsgId1, TopicId1, Payload1), send_publish_msg_normal_topic(Socket, NegQoS, MsgId1, TopicId1, Payload1),
timer:sleep(100), timer:sleep(100),
case ?ENABLE_QOS3 of case ?ENABLE_QOS3 of
true -> true ->
Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId1:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>, Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId1:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
What = receive_response(Socket), What = receive_response(Socket),
?assertEqual(Eexp, What) ?assertEqual(Eexp, What)
end, end,
@ -441,7 +500,9 @@ t_publish_qos0_case01(_) ->
send_publish_msg_normal_topic(Socket, QoS, MsgId1, TopicId1, Payload1), send_publish_msg_normal_topic(Socket, QoS, MsgId1, TopicId1, Payload1),
timer:sleep(100), timer:sleep(100),
Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId1:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>, Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId1:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
What = receive_response(Socket), What = receive_response(Socket),
?assertEqual(Eexp, What), ?assertEqual(Eexp, What),
@ -463,15 +524,21 @@ t_publish_qos0_case02(_) ->
?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
send_subscribe_msg_predefined_topic(Socket, QoS, PredefTopicId, MsgId), send_subscribe_msg_predefined_topic(Socket, QoS, PredefTopicId, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
receive_response(Socket)
),
MsgId1 = 3, MsgId1 = 3,
Payload1 = <<20, 21, 22, 23>>, Payload1 = <<20, 21, 22, 23>>,
send_publish_msg_predefined_topic(Socket, QoS, MsgId1, PredefTopicId, Payload1), send_publish_msg_predefined_topic(Socket, QoS, MsgId1, PredefTopicId, Payload1),
timer:sleep(100), timer:sleep(100),
Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_PREDEFINED_TOPIC:2, PredefTopicId:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>, Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_PREDEFINED_TOPIC:2,
PredefTopicId:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
What = receive_response(Socket), What = receive_response(Socket),
?assertEqual(Eexp, What), ?assertEqual(Eexp, What),
@ -494,15 +561,21 @@ t_publish_qos0_case3(_) ->
Topic = <<"/a/b/c">>, Topic = <<"/a/b/c">>,
send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId), send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
receive_response(Socket)
),
MsgId1 = 3, MsgId1 = 3,
Payload1 = <<20, 21, 22, 23>>, Payload1 = <<20, 21, 22, 23>>,
send_publish_msg_predefined_topic(Socket, QoS, MsgId1, TopicId, Payload1), send_publish_msg_predefined_topic(Socket, QoS, MsgId1, TopicId, Payload1),
timer:sleep(100), timer:sleep(100),
Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>, Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
What = receive_response(Socket), What = receive_response(Socket),
?assertEqual(Eexp, What), ?assertEqual(Eexp, What),
@ -524,8 +597,12 @@ t_publish_qos0_case04(_) ->
?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
send_subscribe_msg_normal_topic(Socket, QoS, <<"#">>, MsgId), send_subscribe_msg_normal_topic(Socket, QoS, <<"#">>, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>,
receive_response(Socket)
),
MsgId1 = 2, MsgId1 = 2,
Payload1 = <<20, 21, 22, 23>>, Payload1 = <<20, 21, 22, 23>>,
@ -533,7 +610,9 @@ t_publish_qos0_case04(_) ->
send_publish_msg_short_topic(Socket, QoS, MsgId1, Topic, Payload1), send_publish_msg_short_topic(Socket, QoS, MsgId1, Topic, Payload1),
timer:sleep(100), timer:sleep(100),
Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_SHORT_TOPIC:2, Topic/binary, (mid(0)):16, <<20, 21, 22, 23>>/binary>>, Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_SHORT_TOPIC:2,
Topic/binary, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
What = receive_response(Socket), What = receive_response(Socket),
?assertEqual(Eexp, What), ?assertEqual(Eexp, What),
@ -554,8 +633,12 @@ t_publish_qos0_case05(_) ->
send_connect_msg(Socket, ClientId), send_connect_msg(Socket, ClientId),
?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
send_subscribe_msg_short_topic(Socket, QoS, <<"/#">>, MsgId), send_subscribe_msg_short_topic(Socket, QoS, <<"/#">>, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>,
receive_response(Socket)
),
send_disconnect_msg(Socket, undefined), send_disconnect_msg(Socket, undefined),
?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)), ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
@ -577,15 +660,21 @@ t_publish_qos0_case06(_) ->
Topic = <<"abc">>, Topic = <<"abc">>,
send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId), send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
receive_response(Socket)
),
MsgId1 = 3, MsgId1 = 3,
Payload1 = <<20, 21, 22, 23>>, Payload1 = <<20, 21, 22, 23>>,
send_publish_msg_normal_topic(Socket, QoS, MsgId1, TopicId1, Payload1), send_publish_msg_normal_topic(Socket, QoS, MsgId1, TopicId1, Payload1),
timer:sleep(100), timer:sleep(100),
Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId1:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>, Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId1:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
What = receive_response(Socket), What = receive_response(Socket),
?assertEqual(Eexp, What), ?assertEqual(Eexp, What),
@ -607,16 +696,24 @@ t_publish_qos1_case01(_) ->
send_connect_msg(Socket, ClientId), send_connect_msg(Socket, ClientId),
?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId), send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?assertEqual(
?SN_NORMAL_TOPIC:2, TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>, <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
receive_response(Socket)), Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
receive_response(Socket)
),
Payload1 = <<20, 21, 22, 23>>, Payload1 = <<20, 21, 22, 23>>,
send_publish_msg_normal_topic(Socket, QoS, MsgId, TopicId1, Payload1), send_publish_msg_normal_topic(Socket, QoS, MsgId, TopicId1, Payload1),
?assertEqual(<<7, ?SN_PUBACK, TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>, receive_response(Socket)), ?assertEqual(<<7, ?SN_PUBACK, TopicId1:16,
MsgId:16, ?SN_RC_ACCEPTED>>, receive_response(Socket)),
timer:sleep(100), timer:sleep(100),
?assertEqual(<<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId1:16, MsgId:16, <<20, 21, 22, 23>>/binary>>, receive_response(Socket)), ?assertEqual(<<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId1:16, MsgId:16, <<20, 21, 22, 23>>/binary>>,
receive_response(Socket)
),
send_disconnect_msg(Socket, undefined), send_disconnect_msg(Socket, undefined),
gen_udp:close(Socket). gen_udp:close(Socket).
@ -635,12 +732,17 @@ t_publish_qos1_case02(_) ->
?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
send_subscribe_msg_predefined_topic(Socket, QoS, PredefTopicId, MsgId), send_subscribe_msg_predefined_topic(Socket, QoS, PredefTopicId, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
receive_response(Socket)
),
Payload1 = <<20, 21, 22, 23>>, Payload1 = <<20, 21, 22, 23>>,
send_publish_msg_predefined_topic(Socket, QoS, MsgId, PredefTopicId, Payload1), send_publish_msg_predefined_topic(Socket, QoS, MsgId, PredefTopicId, Payload1),
?assertEqual(<<7, ?SN_PUBACK, PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>, receive_response(Socket)), ?assertEqual(<<7, ?SN_PUBACK, PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
receive_response(Socket)),
timer:sleep(100), timer:sleep(100),
send_disconnect_msg(Socket, undefined), send_disconnect_msg(Socket, undefined),
@ -655,7 +757,8 @@ t_publish_qos1_case03(_) ->
send_connect_msg(Socket, ClientId), send_connect_msg(Socket, ClientId),
?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
send_publish_msg_predefined_topic(Socket, QoS, MsgId, tid(5), <<20, 21, 22, 23>>), send_publish_msg_predefined_topic(Socket, QoS, MsgId, tid(5), <<20, 21, 22, 23>>),
?assertEqual(<<7, ?SN_PUBACK, TopicId5:16, MsgId:16, ?SN_RC_INVALID_TOPIC_ID>>, receive_response(Socket)), ?assertEqual(<<7, ?SN_PUBACK, TopicId5:16, MsgId:16, ?SN_RC_INVALID_TOPIC_ID>>,
receive_response(Socket)),
send_disconnect_msg(Socket, undefined), send_disconnect_msg(Socket, undefined),
?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)), ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
@ -674,15 +777,19 @@ t_publish_qos1_case04(_) ->
send_connect_msg(Socket, ClientId), send_connect_msg(Socket, ClientId),
?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
send_subscribe_msg_short_topic(Socket, QoS, <<"ab">>, MsgId), send_subscribe_msg_short_topic(Socket, QoS, <<"ab">>, MsgId),
?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?assertEqual(
?SN_NORMAL_TOPIC:2, TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>, <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
receive_response(Socket)), Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>,
receive_response(Socket)
),
Topic = <<"ab">>, Topic = <<"ab">>,
Payload1 = <<20, 21, 22, 23>>, Payload1 = <<20, 21, 22, 23>>,
send_publish_msg_short_topic(Socket, QoS, MsgId, Topic, Payload1), send_publish_msg_short_topic(Socket, QoS, MsgId, Topic, Payload1),
<<TopicIdShort:16>> = Topic, <<TopicIdShort:16>> = Topic,
?assertEqual(<<7, ?SN_PUBACK, TopicIdShort:16, MsgId:16, ?SN_RC_ACCEPTED>>, receive_response(Socket)), ?assertEqual(<<7, ?SN_PUBACK, TopicIdShort:16, MsgId:16, ?SN_RC_ACCEPTED>>,
receive_response(Socket)),
timer:sleep(100), timer:sleep(100),
send_disconnect_msg(Socket, undefined), send_disconnect_msg(Socket, undefined),
@ -708,7 +815,9 @@ t_publish_qos1_case05(_) ->
send_publish_msg_short_topic(Socket, QoS, MsgId, <<"/#">>, <<20, 21, 22, 23>>), send_publish_msg_short_topic(Socket, QoS, MsgId, <<"/#">>, <<20, 21, 22, 23>>),
<<TopicIdShort:16>> = <<"/#">>, <<TopicIdShort:16>> = <<"/#">>,
?assertEqual(<<7, ?SN_PUBACK, TopicIdShort:16, MsgId:16, ?SN_RC_NOT_SUPPORTED>>, receive_response(Socket)), ?assertEqual(<<7, ?SN_PUBACK, TopicIdShort:16,
MsgId:16, ?SN_RC_NOT_SUPPORTED>>,
receive_response(Socket)),
send_disconnect_msg(Socket, undefined), send_disconnect_msg(Socket, undefined),
?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)), ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
@ -734,7 +843,8 @@ t_publish_qos1_case06(_) ->
send_publish_msg_short_topic(Socket, QoS, MsgId, <<"/+">>, <<20, 21, 22, 23>>), send_publish_msg_short_topic(Socket, QoS, MsgId, <<"/+">>, <<20, 21, 22, 23>>),
<<TopicIdShort:16>> = <<"/+">>, <<TopicIdShort:16>> = <<"/+">>,
?assertEqual(<<7, ?SN_PUBACK, TopicIdShort:16, MsgId:16, ?SN_RC_NOT_SUPPORTED>>, receive_response(Socket)), ?assertEqual(<<7, ?SN_PUBACK, TopicIdShort:16,
MsgId:16, ?SN_RC_NOT_SUPPORTED>>, receive_response(Socket)),
send_disconnect_msg(Socket, undefined), send_disconnect_msg(Socket, undefined),
?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)), ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
@ -761,7 +871,12 @@ t_publish_qos2_case01(_) ->
send_publish_msg_normal_topic(Socket, QoS, MsgId, TopicId1, Payload1), send_publish_msg_normal_topic(Socket, QoS, MsgId, TopicId1, Payload1),
?assertEqual(<<4, ?SN_PUBREC, MsgId:16>>, receive_response(Socket)), ?assertEqual(<<4, ?SN_PUBREC, MsgId:16>>, receive_response(Socket)),
send_pubrel_msg(Socket, MsgId), send_pubrel_msg(Socket, MsgId),
?assertEqual(<<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId1:16, 1:16, <<20, 21, 22, 23>>/binary>>, receive_response(Socket)), ?assertEqual(
<<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
TopicId1:16, 1:16, <<20, 21, 22, 23>>/binary>>,
receive_response(Socket)
),
?assertEqual(<<4, ?SN_PUBCOMP, MsgId:16>>, receive_response(Socket)), ?assertEqual(<<4, ?SN_PUBCOMP, MsgId:16>>, receive_response(Socket)),
timer:sleep(100), timer:sleep(100),
@ -783,15 +898,23 @@ t_publish_qos2_case02(_) ->
?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
send_subscribe_msg_predefined_topic(Socket, QoS, PredefTopicId, MsgId), send_subscribe_msg_predefined_topic(Socket, QoS, PredefTopicId, MsgId),
?assertEqual(<<8, ?SN_SUBACK, ?FNU:1, QoS:2, ?FNU:5, PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, ?FNU:1, QoS:2,
?FNU:5, PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
receive_response(Socket)
),
Payload1 = <<20, 21, 22, 23>>, Payload1 = <<20, 21, 22, 23>>,
send_publish_msg_predefined_topic(Socket, QoS, MsgId, PredefTopicId, Payload1), send_publish_msg_predefined_topic(Socket, QoS, MsgId, PredefTopicId, Payload1),
?assertEqual(<<4, ?SN_PUBREC, MsgId:16>>, receive_response(Socket)), ?assertEqual(<<4, ?SN_PUBREC, MsgId:16>>, receive_response(Socket)),
send_pubrel_msg(Socket, MsgId), send_pubrel_msg(Socket, MsgId),
?assertEqual(<<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_PREDEFINED_TOPIC :2, PredefTopicId:16, 1:16, <<20, 21, 22, 23>>/binary>>, receive_response(Socket)), ?assertEqual(
<<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_PREDEFINED_TOPIC:2,
PredefTopicId:16, 1:16, <<20, 21, 22, 23>>/binary>>,
receive_response(Socket)
),
?assertEqual(<<4, ?SN_PUBCOMP, MsgId:16>>, receive_response(Socket)), ?assertEqual(<<4, ?SN_PUBCOMP, MsgId:16>>, receive_response(Socket)),
timer:sleep(100), timer:sleep(100),
@ -814,15 +937,23 @@ t_publish_qos2_case03(_) ->
?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
send_subscribe_msg_normal_topic(Socket, QoS, <<"/#">>, MsgId), send_subscribe_msg_normal_topic(Socket, QoS, <<"/#">>, MsgId),
?assertEqual(<<8, ?SN_SUBACK, ?FNU:1, QoS:2, ?FNU:5, TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>, ?assertEqual(
receive_response(Socket)), <<8, ?SN_SUBACK, ?FNU:1, QoS:2,
?FNU:5, TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>,
receive_response(Socket)
),
Payload1 = <<20, 21, 22, 23>>, Payload1 = <<20, 21, 22, 23>>,
send_publish_msg_short_topic(Socket, QoS, MsgId, <<"/a">>, Payload1), send_publish_msg_short_topic(Socket, QoS, MsgId, <<"/a">>, Payload1),
?assertEqual(<<4, ?SN_PUBREC, MsgId:16>>, receive_response(Socket)), ?assertEqual(<<4, ?SN_PUBREC, MsgId:16>>, receive_response(Socket)),
send_pubrel_msg(Socket, MsgId), send_pubrel_msg(Socket, MsgId),
?assertEqual(<<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_SHORT_TOPIC :2, <<"/a">>/binary, 1:16, <<20, 21, 22, 23>>/binary>>, receive_response(Socket)), ?assertEqual(
<<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
Will:1, CleanSession:1, ?SN_SHORT_TOPIC:2,
<<"/a">>/binary, 1:16, <<20, 21, 22, 23>>/binary>>,
receive_response(Socket)
),
?assertEqual(<<4, ?SN_PUBCOMP, MsgId:16>>, receive_response(Socket)), ?assertEqual(<<4, ?SN_PUBCOMP, MsgId:16>>, receive_response(Socket)),
timer:sleep(100), timer:sleep(100),
@ -1095,7 +1226,12 @@ t_will_case06(_) ->
% send_register_msg(Socket, TopicName1, MsgId1), % send_register_msg(Socket, TopicName1, MsgId1),
% ?assertEqual(<<7, ?SN_REGACK, TopicId1:16, MsgId1:16, 0:8>>, receive_response(Socket)), % ?assertEqual(<<7, ?SN_REGACK, TopicId1:16, MsgId1:16, 0:8>>, receive_response(Socket)),
% send_subscribe_msg_predefined_topic(Socket, QoS, TopicId1, MsgId), % send_subscribe_msg_predefined_topic(Socket, QoS, TopicId1, MsgId),
% ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId1:16, MsgId:16, ReturnCode>>, receive_response(Socket)), % ?assertEqual(
% <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
% WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
% TopicId1:16, MsgId:16, ReturnCode>>,
% receive_response(Socket)
% ),
% %
% % goto asleep state % % goto asleep state
% send_disconnect_msg(Socket, 1), % send_disconnect_msg(Socket, 1),
@ -1121,7 +1257,9 @@ t_will_case06(_) ->
% %
% %% the broker should sent dl msgs to the awake client before sending the pingresp % %% the broker should sent dl msgs to the awake client before sending the pingresp
% UdpData = receive_response(Socket), % UdpData = receive_response(Socket),
% MsgId_udp = check_publish_msg_on_udp({Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicId1, Payload1}, UdpData), % MsgId_udp = check_publish_msg_on_udp(
% {Dup, QoS, Retain, WillBit, CleanSession,
% ?SN_NORMAL_TOPIC, TopicId1, Payload1}, UdpData),
% send_puback_msg(Socket, TopicId1, MsgId_udp), % send_puback_msg(Socket, TopicId1, MsgId_udp),
% %
% %% check the pingresp is received at last % %% check the pingresp is received at last
@ -1153,8 +1291,12 @@ t_will_case06(_) ->
% CleanSession = 0, % CleanSession = 0,
% ReturnCode = 0, % ReturnCode = 0,
% send_subscribe_msg_normal_topic(Socket, QoS, TopicName1, MsgId1), % send_subscribe_msg_normal_topic(Socket, QoS, TopicName1, MsgId1),
% ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId0:16, MsgId1:16, ReturnCode>>, % ?assertEqual(
% receive_response(Socket)), % <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
% WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
% TopicId0:16, MsgId1:16, ReturnCode>>,
% receive_response(Socket)
% ),
% %
% % goto asleep state % % goto asleep state
% send_disconnect_msg(Socket, 1), % send_disconnect_msg(Socket, 1),
@ -1188,11 +1330,15 @@ t_will_case06(_) ->
% send_regack_msg(Socket, TopicIdNew, MsgId3), % send_regack_msg(Socket, TopicIdNew, MsgId3),
% %
% UdpData2 = receive_response(Socket), % UdpData2 = receive_response(Socket),
% MsgId_udp2 = check_publish_msg_on_udp({Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload1}, UdpData2), % MsgId_udp2 = check_publish_msg_on_udp(
% {Dup, QoS, Retain, WillBit, CleanSession,
% ?SN_NORMAL_TOPIC, TopicIdNew, Payload1}, UdpData2),
% send_puback_msg(Socket, TopicIdNew, MsgId_udp2), % send_puback_msg(Socket, TopicIdNew, MsgId_udp2),
% %
% UdpData3 = receive_response(Socket), % UdpData3 = receive_response(Socket),
% MsgId_udp3 = check_publish_msg_on_udp({Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload2}, UdpData3), % MsgId_udp3 = check_publish_msg_on_udp(
% {Dup, QoS, Retain, WillBit, CleanSession,
% ?SN_NORMAL_TOPIC, TopicIdNew, Payload2}, UdpData3),
% send_puback_msg(Socket, TopicIdNew, MsgId_udp3), % send_puback_msg(Socket, TopicIdNew, MsgId_udp3),
% %
% ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)), % ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)),
@ -1228,8 +1374,12 @@ receive_publish(Socket) ->
% CleanSession = 0, % CleanSession = 0,
% ReturnCode = 0, % ReturnCode = 0,
% send_subscribe_msg_normal_topic(Socket, QoS, TopicName1, MsgId1), % send_subscribe_msg_normal_topic(Socket, QoS, TopicName1, MsgId1),
% ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId0:16, MsgId1:16, ReturnCode>>, % ?assertEqual(
% receive_response(Socket)), % <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
% WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
% TopicId0:16, MsgId1:16, ReturnCode>>,
% receive_response(Socket)
% ),
% %
% % goto asleep state % % goto asleep state
% SleepDuration = 30, % SleepDuration = 30,
@ -1262,12 +1412,16 @@ receive_publish(Socket) ->
% send_regack_msg(Socket, TopicIdNew, MsgId_reg), % send_regack_msg(Socket, TopicIdNew, MsgId_reg),
% %
% UdpData2 = receive_response(Socket), % UdpData2 = receive_response(Socket),
% MsgId2 = check_publish_msg_on_udp({Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload2}, UdpData2), % MsgId2 = check_publish_msg_on_udp(
% {Dup, QoS, Retain, WillBit, CleanSession,
% ?SN_NORMAL_TOPIC, TopicIdNew, Payload2}, UdpData2),
% send_puback_msg(Socket, TopicIdNew, MsgId2), % send_puback_msg(Socket, TopicIdNew, MsgId2),
% timer:sleep(50), % timer:sleep(50),
% %
% UdpData3 = wrap_receive_response(Socket), % UdpData3 = wrap_receive_response(Socket),
% MsgId3 = check_publish_msg_on_udp({Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload3}, UdpData3), % MsgId3 = check_publish_msg_on_udp(
% {Dup, QoS, Retain, WillBit, CleanSession,
% ?SN_NORMAL_TOPIC, TopicIdNew, Payload3}, UdpData3),
% send_puback_msg(Socket, TopicIdNew, MsgId3), % send_puback_msg(Socket, TopicIdNew, MsgId3),
% timer:sleep(50), % timer:sleep(50),
% %
@ -1334,7 +1488,9 @@ receive_publish(Socket) ->
% send_pingreq_msg(Socket, ClientId), % send_pingreq_msg(Socket, ClientId),
% %
% UdpData = wrap_receive_response(Socket), % UdpData = wrap_receive_response(Socket),
% MsgId_udp = check_publish_msg_on_udp({Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicId_tom, Payload1}, UdpData), % MsgId_udp = check_publish_msg_on_udp(
% {Dup, QoS, Retain, WillBit, CleanSession,
% ?SN_NORMAL_TOPIC, TopicId_tom, Payload1}, UdpData),
% send_pubrec_msg(Socket, MsgId_udp), % send_pubrec_msg(Socket, MsgId_udp),
% ?assertMatch(<<_:8, ?SN_PUBREL:8, _/binary>>, receive_response(Socket)), % ?assertMatch(<<_:8, ?SN_PUBREL:8, _/binary>>, receive_response(Socket)),
% send_pubcomp_msg(Socket, MsgId_udp), % send_pubcomp_msg(Socket, MsgId_udp),
@ -1369,8 +1525,12 @@ receive_publish(Socket) ->
% send_register_msg(Socket, TopicName_tom, MsgId1), % send_register_msg(Socket, TopicName_tom, MsgId1),
% TopicId_tom = check_regack_msg_on_udp(MsgId1, receive_response(Socket)), % TopicId_tom = check_regack_msg_on_udp(MsgId1, receive_response(Socket)),
% send_subscribe_msg_predefined_topic(Socket, QoS, TopicId_tom, MsgId1), % send_subscribe_msg_predefined_topic(Socket, QoS, TopicId_tom, MsgId1),
% ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId_tom:16, MsgId1:16, ReturnCode>>, % ?assertEqual(
% receive_response(Socket)), % <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
% WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
% TopicId_tom:16, MsgId1:16, ReturnCode>>,
% receive_response(Socket)
% ),
% %
% % goto asleep state % % goto asleep state
% send_disconnect_msg(Socket, SleepDuration), % send_disconnect_msg(Socket, SleepDuration),
@ -1448,8 +1608,12 @@ receive_publish(Socket) ->
% CleanSession = 0, % CleanSession = 0,
% ReturnCode = 0, % ReturnCode = 0,
% send_subscribe_msg_normal_topic(Socket, QoS, TopicName1, MsgId1), % send_subscribe_msg_normal_topic(Socket, QoS, TopicName1, MsgId1),
% ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId0:16, MsgId1:16, ReturnCode>>, % ?assertEqual(
% receive_response(Socket)), % <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
% WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
% TopicId0:16, MsgId1:16, ReturnCode>>,
% receive_response(Socket)
% ),
% % goto asleep state % % goto asleep state
% SleepDuration = 30, % SleepDuration = 30,
% send_disconnect_msg(Socket, SleepDuration), % send_disconnect_msg(Socket, SleepDuration),
@ -1483,7 +1647,9 @@ receive_publish(Socket) ->
% udp_receive_timeout -> % udp_receive_timeout ->
% ok; % ok;
% UdpData2 -> % UdpData2 ->
% MsgId2 = check_publish_msg_on_udp({Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload2}, UdpData2), % MsgId2 = check_publish_msg_on_udp(
% {Dup, QoS, Retain, WillBit, CleanSession,
% ?SN_NORMAL_TOPIC, TopicIdNew, Payload2}, UdpData2),
% send_puback_msg(Socket, TopicIdNew, MsgId2) % send_puback_msg(Socket, TopicIdNew, MsgId2)
% end, % end,
% timer:sleep(100), % timer:sleep(100),
@ -1492,7 +1658,9 @@ receive_publish(Socket) ->
% udp_receive_timeout -> % udp_receive_timeout ->
% ok; % ok;
% UdpData3 -> % UdpData3 ->
% MsgId3 = check_publish_msg_on_udp({Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload3}, UdpData3), % MsgId3 = check_publish_msg_on_udp(
% {Dup, QoS, Retain, WillBit, CleanSession,
% ?SN_NORMAL_TOPIC, TopicIdNew, Payload3}, UdpData3),
% send_puback_msg(Socket, TopicIdNew, MsgId3) % send_puback_msg(Socket, TopicIdNew, MsgId3)
% end, % end,
% timer:sleep(100), % timer:sleep(100),
@ -1510,7 +1678,8 @@ receive_publish(Socket) ->
% %
% %% send PINGREQ again to enter awake state % %% send PINGREQ again to enter awake state
% send_pingreq_msg(Socket, ClientId), % send_pingreq_msg(Socket, ClientId),
% %% will not receive any buffered PUBLISH messages buffered before last awake, only receive PINGRESP here % %% will not receive any buffered PUBLISH messages buffered before last
% %% awake, only receive PINGRESP here
% ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)), % ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)),
% %
% gen_udp:close(Socket). % gen_udp:close(Socket).
@ -1913,8 +2082,11 @@ check_dispatched_message(Dup, QoS, Retain, TopicIdType, TopicId, Payload, Socket
PubMsg = receive_response(Socket), PubMsg = receive_response(Socket),
Length = 7 + byte_size(Payload), Length = 7 + byte_size(Payload),
?LOG("check_dispatched_message ~p~n", [PubMsg]), ?LOG("check_dispatched_message ~p~n", [PubMsg]),
?LOG("expected ~p xx ~p~n", [<<Length, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, ?FNU:2, TopicIdType:2, TopicId:16>>, Payload]), ?LOG("expected ~p xx ~p~n",
<<Length, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, ?FNU:2, TopicIdType:2, TopicId:16, MsgId:16, Payload/binary>> = PubMsg, [<<Length, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
?FNU:2, TopicIdType:2, TopicId:16>>, Payload]),
<<Length, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
?FNU:2, TopicIdType:2, TopicId:16, MsgId:16, Payload/binary>> = PubMsg,
case QoS of case QoS of
0 -> ok; 0 -> ok;
1 -> send_puback_msg(Socket, TopicId, MsgId); 1 -> send_puback_msg(Socket, TopicId, MsgId);
@ -1926,11 +2098,13 @@ check_dispatched_message(Dup, QoS, Retain, TopicIdType, TopicId, Payload, Socket
get_udp_broadcast_address() -> get_udp_broadcast_address() ->
"255.255.255.255". "255.255.255.255".
check_publish_msg_on_udp({Dup, QoS, Retain, WillBit, CleanSession, TopicType, TopicId, Payload}, UdpData) -> check_publish_msg_on_udp({Dup, QoS, Retain, WillBit, CleanSession,
TopicType, TopicId, Payload}, UdpData) ->
<<HeaderUdp:5/binary, MsgId:16, PayloadIn/binary>> = UdpData, <<HeaderUdp:5/binary, MsgId:16, PayloadIn/binary>> = UdpData,
ct:pal("UdpData: ~p, Payload: ~p, PayloadIn: ~p", [UdpData, Payload, PayloadIn]), ct:pal("UdpData: ~p, Payload: ~p, PayloadIn: ~p", [UdpData, Payload, PayloadIn]),
Size9 = byte_size(Payload) + 7, Size9 = byte_size(Payload) + 7,
Eexp = <<Size9:8, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1, TopicType:2, TopicId:16>>, Eexp = <<Size9:8, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
WillBit:1, CleanSession:1, TopicType:2, TopicId:16>>,
?assertEqual(Eexp, HeaderUdp), % mqtt-sn header should be same ?assertEqual(Eexp, HeaderUdp), % mqtt-sn header should be same
?assertEqual(Payload, PayloadIn), % payload should be same ?assertEqual(Payload, PayloadIn), % payload should be same
MsgId. MsgId.

View File

@ -148,7 +148,6 @@ init_per_testcase(t_events, Config) ->
#{id => <<"rule:t_events">>, #{id => <<"rule:t_events">>,
sql => SQL, sql => SQL,
outputs => [ outputs => [
#{function => console},
#{function => <<"emqx_rule_engine_SUITE:output_record_triggered_events">>, #{function => <<"emqx_rule_engine_SUITE:output_record_triggered_events">>,
args => #{}} args => #{}}
], ],

View File

@ -587,6 +587,7 @@ case "${COMMAND}" in
ping) ping)
assert_node_alive assert_node_alive
echo pong
;; ;;
escript) escript)

8
build
View File

@ -52,13 +52,10 @@ log() {
} }
docgen() { docgen() {
local conf_doc_html libs_dir1 libs_dir2 local libs_dir1 libs_dir2
conf_doc_html="$(pwd)/_build/${PROFILE}/rel/emqx/etc/emqx-config-doc.html"
echo "===< Generating config document $conf_doc_html"
libs_dir1="$(find "_build/default/lib/" -maxdepth 2 -name ebin -type d)" libs_dir1="$(find "_build/default/lib/" -maxdepth 2 -name ebin -type d)"
libs_dir2="$(find "_build/$PROFILE/lib/" -maxdepth 2 -name ebin -type d)" libs_dir2="$(find "_build/$PROFILE/lib/" -maxdepth 2 -name ebin -type d)"
# shellcheck disable=SC2086
erl -noshell -pa $libs_dir1 $libs_dir2 -eval "file:write_file('$conf_doc_html', hocon_schema_html:gen(emqx_conf_schema, \"EMQ X ${PKG_VSN}\")), halt(0)."
local conf_doc_markdown local conf_doc_markdown
conf_doc_markdown="$(pwd)/_build/${PROFILE}/rel/emqx/etc/emqx-config-doc.md" conf_doc_markdown="$(pwd)/_build/${PROFILE}/rel/emqx/etc/emqx-config-doc.md"
echo "===< Generating config document $conf_doc_markdown" echo "===< Generating config document $conf_doc_markdown"
@ -146,6 +143,7 @@ make_zip() {
## for DEB and RPM packages the dependencies are resoved by yum and apt ## for DEB and RPM packages the dependencies are resoved by yum and apt
cp_dyn_libs "${tard}/emqx" cp_dyn_libs "${tard}/emqx"
(cd "${tard}" && zip -qr - emqx) > "${zipball}" (cd "${tard}" && zip -qr - emqx) > "${zipball}"
log "Zip package successfully created: ${zipball}"
} }
## This function builds the default docker image based on alpine:3.14 (by default) ## This function builds the default docker image based on alpine:3.14 (by default)

View File

@ -28,6 +28,7 @@ RUN cd /emqx \
FROM $RUN_FROM FROM $RUN_FROM
## define ARG again after 'FROM $RUN_FROM'
ARG EMQX_NAME=emqx ARG EMQX_NAME=emqx
COPY deploy/docker/docker-entrypoint.sh /usr/bin/ COPY deploy/docker/docker-entrypoint.sh /usr/bin/

View File

@ -148,44 +148,52 @@ prod_overrides() ->
profiles() -> profiles() ->
Vsn = get_vsn(), Vsn = get_vsn(),
[ {'emqx', [ {erl_opts, prod_compile_opts()} [ {'emqx',
, {relx, relx(Vsn, cloud, bin, ce)} [ {erl_opts, prod_compile_opts()}
, {overrides, prod_overrides()} , {relx, relx(Vsn, cloud, bin, ce)}
, {project_app_dirs, project_app_dirs(ce)} , {overrides, prod_overrides()}
]} , {project_app_dirs, project_app_dirs(ce)}
, {'emqx-pkg', [ {erl_opts, prod_compile_opts()} ]}
, {relx, relx(Vsn, cloud, pkg, ce)} , {'emqx-pkg',
, {overrides, prod_overrides()} [ {erl_opts, prod_compile_opts()}
, {project_app_dirs, project_app_dirs(ce)} , {relx, relx(Vsn, cloud, pkg, ce)}
]} , {overrides, prod_overrides()}
, {'emqx-ee', [ {erl_opts, prod_compile_opts()} , {project_app_dirs, project_app_dirs(ce)}
, {relx, relx(Vsn, cloud, bin, ee)} ]}
, {overrides, prod_overrides()} , {'emqx-enterprise',
, {project_app_dirs, project_app_dirs(ee)} [ {erl_opts, prod_compile_opts()}
]} , {relx, relx(Vsn, cloud, bin, ee)}
, {'emqx-ee-pkg', [ {erl_opts, prod_compile_opts()} , {overrides, prod_overrides()}
, {relx, relx(Vsn, cloud, pkg, ee)} , {project_app_dirs, project_app_dirs(ee)}
, {overrides, prod_overrides()} ]}
, {project_app_dirs, project_app_dirs(ee)} , {'emqx-enterprise-pkg',
]} [ {erl_opts, prod_compile_opts()}
, {'emqx-edge', [ {erl_opts, prod_compile_opts()} , {relx, relx(Vsn, cloud, pkg, ee)}
, {relx, relx(Vsn, edge, bin, ce)} , {overrides, prod_overrides()}
, {overrides, prod_overrides()} , {project_app_dirs, project_app_dirs(ee)}
, {project_app_dirs, project_app_dirs(ce)} ]}
]} , {'emqx-edge',
, {'emqx-edge-pkg', [ {erl_opts, prod_compile_opts()} [ {erl_opts, prod_compile_opts()}
, {relx, relx(Vsn, edge, pkg, ce)} , {relx, relx(Vsn, edge, bin, ce)}
, {overrides, prod_overrides()} , {overrides, prod_overrides()}
, {project_app_dirs, project_app_dirs(ce)} , {project_app_dirs, project_app_dirs(ce)}
]} ]}
, {check, [ {erl_opts, common_compile_opts()} , {'emqx-edge-pkg',
, {project_app_dirs, project_app_dirs(ce)} [ {erl_opts, prod_compile_opts()}
]} , {relx, relx(Vsn, edge, pkg, ce)}
, {test, [ {deps, test_deps()} , {overrides, prod_overrides()}
, {erl_opts, common_compile_opts() ++ erl_opts_i(ce) } , {project_app_dirs, project_app_dirs(ce)}
, {extra_src_dirs, [{"test", [{recursive, true}]}]} ]}
, {project_app_dirs, project_app_dirs(ce)} , {check,
]} [ {erl_opts, common_compile_opts()}
, {project_app_dirs, project_app_dirs(ce)}
]}
, {test,
[ {deps, test_deps()}
, {erl_opts, common_compile_opts() ++ erl_opts_i(ce) }
, {extra_src_dirs, [{"test", [{recursive, true}]}]}
, {project_app_dirs, project_app_dirs(ce)}
]}
]. ].
%% RelType: cloud (full size) | edge (slim size) %% RelType: cloud (full size) | edge (slim size)

View File

@ -154,7 +154,7 @@ download_prev_release(Tag, #{binary_rel_url := {ok, URL0}, clone_url := Repo}) -
Dir = filename:basename(Repo, ".git") ++ [$-|Tag], Dir = filename:basename(Repo, ".git") ++ [$-|Tag],
Filename = filename:join(BaseDir, Dir), Filename = filename:join(BaseDir, Dir),
Script = "mkdir -p ${OUTFILE} && Script = "mkdir -p ${OUTFILE} &&
wget -O ${OUTFILE}.zip ${URL} && wget -c -O ${OUTFILE}.zip ${URL} &&
unzip -n -d ${OUTFILE} ${OUTFILE}.zip", unzip -n -d ${OUTFILE} ${OUTFILE}.zip",
Env = [{"TAG", Tag}, {"OUTFILE", Filename}, {"URL", URL}], Env = [{"TAG", Tag}, {"OUTFILE", Filename}, {"URL", URL}],
bash(Script, Env), bash(Script, Env),
@ -298,12 +298,15 @@ render_appfile(File, Upgrade, Downgrade) ->
ok = file:write_file(File, IOList). ok = file:write_file(File, IOList).
create_stub(App) -> create_stub(App) ->
case locate(src, App, ".app.src") of Ext = ".app.src",
case locate(src, App, Ext) of
{ok, AppSrc} -> {ok, AppSrc} ->
AppupFile = filename:basename(AppSrc) ++ ".appup.src", DirName = filename:dirname(AppSrc),
AppupFile = filename:basename(AppSrc, Ext) ++ ".appup.src",
Default = {<<".*">>, []}, Default = {<<".*">>, []},
render_appfile(AppupFile, [Default], [Default]), AppupFileFullpath = filename:join(DirName, AppupFile),
AppupFile; render_appfile(AppupFileFullpath, [Default], [Default]),
{ok, AppupFileFullpath};
undefined -> undefined ->
false false
end. end.