Merge the emqx-common, emqx-router libraries

This commit is contained in:
Feng Lee 2018-02-26 23:29:53 +08:00
parent d5893ba2be
commit f4fd6efe16
32 changed files with 1621 additions and 519 deletions

1201
erlang.mk vendored

File diff suppressed because it is too large Load Diff

View File

@ -578,7 +578,7 @@ mqtt.bridge.max_queue_len = 10000
mqtt.bridge.ping_down_interval = 1s mqtt.bridge.ping_down_interval = 1s
##------------------------------------------------------------------- ##-------------------------------------------------------------------
## MQTT Plugins ## Plugins
##------------------------------------------------------------------- ##-------------------------------------------------------------------
## The etc dir for plugins' config. ## The etc dir for plugins' config.
@ -595,7 +595,52 @@ mqtt.plugins.loaded_file = {{ platform_data_dir }}/loaded_plugins
mqtt.plugins.expand_plugins_dir = {{ platform_plugins_dir }}/ mqtt.plugins.expand_plugins_dir = {{ platform_plugins_dir }}/
##-------------------------------------------------------------------- ##--------------------------------------------------------------------
## MQTT Listeners ## Modules
##--------------------------------------------------------------------
##--------------------------------------------------------------------
## Presence Module
## Enable Presence Module.
##
## Value: on | off
module.presence = on
## Sets the QoS for presence MQTT message.
##
## Value: 0 | 1 | 2
module.presence.qos = 1
##--------------------------------------------------------------------
## Subscription Module
## Enable Subscription Module.
##
## Value: on | off
module.subscription = off
## Subscribe the Topics automatically when client connected.
## module.subscription.1.topic = $client/%c
## Qos of the subscription: 0 | 1 | 2
## module.subscription.1.qos = 1
## module.subscription.2.topic = $user/%u
## module.subscription.2.qos = 1
##--------------------------------------------------------------------
## Rewrite Module
## Enable Rewrite Module.
##
## Value: on | off
module.rewrite = off
## {rewrite, Topic, Re, Dest}
## module.rewrite.rule.1 = x/# ^x/y/(.+)$ z/y/$1
## module.rewrite.rule.2 = y/+/z/# ^y/(.+)/z/(.+)$ y/z/$2
##--------------------------------------------------------------------
## Listeners
##-------------------------------------------------------------------- ##--------------------------------------------------------------------
##-------------------------------------------------------------------- ##--------------------------------------------------------------------

View File

@ -156,15 +156,12 @@
-type(mqtt_delivery() :: #mqtt_delivery{}). -type(mqtt_delivery() :: #mqtt_delivery{}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% MQTT Route %% Route
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-record(mqtt_route, -record(route, { topic :: binary(), node :: node() }).
{ topic :: binary(),
node :: node()
}).
-type(mqtt_route() :: #mqtt_route{}). -type(route() :: #route{}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% MQTT Alarm %% MQTT Alarm

13
include/emqx_common.hrl Normal file
View File

@ -0,0 +1,13 @@
-define(record_to_map(Def, Rec),
maps:from_list(?record_to_proplist(Def, Rec))).
-define(record_to_map(Def, Rec, Fields),
maps:from_list(?record_to_proplist(Def, Rec, Fields))).
-define(record_to_proplist(Def, Rec),
lists:zip(record_info(fields, Def), tl(tuple_to_list(Rec)))).
-define(record_to_proplist(Def, Rec, Fields),
[{K, V} || {K, V} <- ?record_to_proplist(Def, Rec), lists:member(K, Fields)]).

View File

@ -1,5 +1,5 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) %% Copyright (c) 2013-2018 EMQ Enterprise, Inc.
%% %%
%% Licensed under the Apache License, Version 2.0 (the "License"); %% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %% you may not use this file except in compliance with the License.
@ -20,7 +20,7 @@
{ node_id :: trie_node_id(), { node_id :: trie_node_id(),
edge_count = 0 :: non_neg_integer(), edge_count = 0 :: non_neg_integer(),
topic :: binary() | undefined, topic :: binary() | undefined,
flags :: [retained | static] flags :: list(atom())
}). }).
-record(trie_edge, -record(trie_edge,

View File

@ -762,7 +762,7 @@ end}.
end}. end}.
%%------------------------------------------------------------------- %%-------------------------------------------------------------------
%% MQTT Plugins %% Plugins
%%------------------------------------------------------------------- %%-------------------------------------------------------------------
{mapping, "mqtt.plugins.etc_dir", "emqx.plugins_etc_dir", [ {mapping, "mqtt.plugins.etc_dir", "emqx.plugins_etc_dir", [
@ -778,7 +778,78 @@ end}.
]}. ]}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% MQTT Listeners %% Modules
%%--------------------------------------------------------------------
{mapping, "module.presence", "emqx.modules", [
{default, off},
{datatype, flag}
]}.
{mapping, "module.presence.qos", "emqx.modules", [
{default, 1},
{datatype, integer},
{validators, ["range:0-2"]}
]}.
{mapping, "module.subscription", "emqx.modules", [
{default, off},
{datatype, flag}
]}.
{mapping, "module.subscription.$id.topic", "emqx.modules", [
{datatype, string}
]}.
{mapping, "module.subscription.$id.qos", "emqx.modules", [
{default, 1},
{datatype, integer},
{validators, ["range:0-2"]}
]}.
{mapping, "module.rewrite", "emqx.modules", [
{default, off},
{datatype, flag}
]}.
{mapping, "module.rewrite.rule.$id", "emqx.modules", [
{datatype, string}
]}.
{translation, "emqx.modules", fun(Conf) ->
Subscriptions = fun() ->
List = cuttlefish_variable:filter_by_prefix("module.subscription", Conf),
QosList = [Qos || {_, Qos} <- lists:sort([{I, Qos} || {[_,"subscription", I,"qos"], Qos} <- List])],
TopicList = [iolist_to_binary(Topic) || {_, Topic} <-
lists:sort([{I, Topic} || {[_,"subscription", I, "topic"], Topic} <- List])],
lists:zip(TopicList, QosList)
end,
Rewrites = fun() ->
Rules = cuttlefish_variable:filter_by_prefix("module.rewrite.rule", Conf),
lists:map(fun({[_, "rewrite", "rule", I], Rule}) ->
[Topic, Re, Dest] = string:tokens(Rule, " "),
{rewrite, list_to_binary(Topic), list_to_binary(Re), list_to_binary(Dest)}
end, Rules)
end,
lists:append([
case cuttlefish:conf_get("module.presence", Conf) of %% Presence
true -> [{emqx_mod_presence, [{qos, cuttlefish:conf_get("module.presence.qos", Conf, 1)}]}];
false -> []
end,
case cuttlefish:conf_get("module.subscription", Conf) of %% Subscription
true -> [{emqx_mod_subscription, Subscriptions()}];
false -> []
end,
case cuttlefish:conf_get("module.rewrite", Conf) of %% Rewrite
true -> [{emqx_mod_rewrite, Rewrites()}];
false -> []
end
])
end}.
%%--------------------------------------------------------------------
%% Listeners
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -38,6 +38,7 @@ start(_Type, _Args) ->
ekka:start(), ekka:start(),
{ok, Sup} = emqx_sup:start_link(), {ok, Sup} = emqx_sup:start_link(),
ok = register_acl_mod(), ok = register_acl_mod(),
emqx_modules:load(),
start_autocluster(), start_autocluster(),
register(emqx, self()), register(emqx, self()),
print_vsn(), print_vsn(),
@ -45,6 +46,7 @@ start(_Type, _Args) ->
-spec(stop(State :: term()) -> term()). -spec(stop(State :: term()) -> term()).
stop(_State) -> stop(_State) ->
emqx_modules:unload(),
catch emqx:stop_listeners(). catch emqx:stop_listeners().
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -1,5 +1,5 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) %% Copyright (c) 2013-2018 EMQ Enterprise, Inc.
%% %%
%% Licensed under the Apache License, Version 2.0 (the "License"); %% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %% you may not use this file except in compliance with the License.
@ -16,8 +16,6 @@
-module(emqx_base62). -module(emqx_base62).
-author("Feng Lee <feng@emqtt.io>").
-export([encode/1, decode/1]). -export([encode/1, decode/1]).
%% @doc Encode an integer to base62 string %% @doc Encode an integer to base62 string

View File

@ -16,8 +16,6 @@
-module(emqx_boot). -module(emqx_boot).
-author("Feng Lee <feng@emqtt.io>").
-export([apply_module_attributes/1, all_module_attributes/1]). -export([apply_module_attributes/1, all_module_attributes/1]).
%% only {F, Args}... %% only {F, Args}...

View File

@ -18,8 +18,6 @@
-module(emqx_gc). -module(emqx_gc).
-author("Feng Lee <feng@emqtt.io>").
-export([conn_max_gc_count/0, reset_conn_gc_count/2, maybe_force_gc/2, -export([conn_max_gc_count/0, reset_conn_gc_count/2, maybe_force_gc/2,
maybe_force_gc/3]). maybe_force_gc/3]).

View File

@ -1,5 +1,5 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) %% Copyright (c) 2013-2018 EMQ Enterprise, Inc.
%% %%
%% Licensed under the Apache License, Version 2.0 (the "License"); %% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %% you may not use this file except in compliance with the License.
@ -14,12 +14,8 @@
%% limitations under the License. %% limitations under the License.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Client Keepalive
-module(emqx_keepalive). -module(emqx_keepalive).
-author("Feng Lee <feng@emqtt.io>").
-export([start/3, check/1, cancel/1]). -export([start/3, check/1, cancel/1]).
-record(keepalive, {statfun, statval, tsec, tmsg, tref, repeat = 0}). -record(keepalive, {statfun, statval, tsec, tmsg, tref, repeat = 0}).

View File

@ -1,5 +1,5 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) %% Copyright (c) 2013-2018 EMQ Enterprise, Inc.
%% %%
%% Licensed under the Apache License, Version 2.0 (the "License"); %% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %% you may not use this file except in compliance with the License.
@ -16,8 +16,6 @@
-module(emqx_misc). -module(emqx_misc).
-author("Feng Lee <feng@emqtt.io>").
-export([merge_opts/2, start_timer/2, start_timer/3, cancel_timer/1, -export([merge_opts/2, start_timer/2, start_timer/3, cancel_timer/1,
proc_stats/0, proc_stats/1]). proc_stats/0, proc_stats/1]).

73
src/emqx_mod_presence.erl Normal file
View File

@ -0,0 +1,73 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io)
%%
%% 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_mod_presence).
-behaviour(emqx_gen_mod).
-include("emqx.hrl").
-export([load/1, unload/1]).
-export([on_client_connected/3, on_client_disconnected/3]).
load(Env) ->
emqx:hook('client.connected', fun ?MODULE:on_client_connected/3, [Env]),
emqx:hook('client.disconnected', fun ?MODULE:on_client_disconnected/3, [Env]).
on_client_connected(ConnAck, Client = #mqtt_client{client_id = ClientId,
username = Username,
peername = {IpAddr, _},
clean_sess = CleanSess,
proto_ver = ProtoVer}, Env) ->
Payload = mochijson2:encode([{clientid, ClientId},
{username, Username},
{ipaddress, iolist_to_binary(emqx_net:ntoa(IpAddr))},
{clean_sess, CleanSess},
{protocol, ProtoVer},
{connack, ConnAck},
{ts, emqx_time:now_secs()}]),
Msg = message(qos(Env), topic(connected, ClientId), Payload),
emqx:publish(emqx_message:set_flag(sys, Msg)),
{ok, Client}.
on_client_disconnected(Reason, #mqtt_client{client_id = ClientId,
username = Username}, Env) ->
Payload = mochijson2:encode([{clientid, ClientId},
{username, Username},
{reason, reason(Reason)},
{ts, emqx_time:now_secs()}]),
Msg = message(qos(Env), topic(disconnected, ClientId), Payload),
emqx:publish(emqx_message:set_flag(sys, Msg)), ok.
unload(_Env) ->
emqx:unhook('client.connected', fun ?MODULE:on_client_connected/3),
emqx:unhook('client.disconnected', fun ?MODULE:on_client_disconnected/3).
message(Qos, Topic, Payload) ->
emqx_message:make(presence, Qos, Topic, iolist_to_binary(Payload)).
topic(connected, ClientId) ->
emqx_topic:systop(list_to_binary(["clients/", ClientId, "/connected"]));
topic(disconnected, ClientId) ->
emqx_topic:systop(list_to_binary(["clients/", ClientId, "/disconnected"])).
qos(Env) -> proplists:get_value(qos, Env, 0).
reason(Reason) when is_atom(Reason) -> Reason;
reason({Error, _}) when is_atom(Error) -> Error;
reason(_) -> internal_error.

91
src/emqx_mod_rewrite.erl Normal file
View File

@ -0,0 +1,91 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io)
%%
%% 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_mod_rewrite).
-include_lib("emqx.hrl").
-export([load/1, unload/1]).
-export([rewrite_subscribe/4, rewrite_unsubscribe/4, rewrite_publish/2]).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
load(Rules0) ->
Rules = compile(Rules0),
emqx:hook('client.subscribe', fun ?MODULE:rewrite_subscribe/4, [Rules]),
emqx:hook('client.unsubscribe',fun ?MODULE:rewrite_unsubscribe/4, [Rules]),
emqx:hook('message.publish', fun ?MODULE:rewrite_publish/2, [Rules]).
rewrite_subscribe(_ClientId, _Username, TopicTable, Rules) ->
lager:info("Rewrite subscribe: ~p", [TopicTable]),
{ok, [{match_rule(Topic, Rules), Opts} || {Topic, Opts} <- TopicTable]}.
rewrite_unsubscribe(_ClientId, _Username, TopicTable, Rules) ->
lager:info("Rewrite unsubscribe: ~p", [TopicTable]),
{ok, [{match_rule(Topic, Rules), Opts} || {Topic, Opts} <- TopicTable]}.
rewrite_publish(Message=#mqtt_message{topic = Topic}, Rules) ->
%%TODO: this will not work if the client is always online.
RewriteTopic =
case get({rewrite, Topic}) of
undefined ->
DestTopic = match_rule(Topic, Rules),
put({rewrite, Topic}, DestTopic), DestTopic;
DestTopic ->
DestTopic
end,
{ok, Message#mqtt_message{topic = RewriteTopic}}.
unload(_) ->
emqx:unhook('client.subscribe', fun ?MODULE:rewrite_subscribe/4),
emqx:unhook('client.unsubscribe',fun ?MODULE:rewrite_unsubscribe/4),
emqx:unhook('message.publish', fun ?MODULE:rewrite_publish/2).
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
match_rule(Topic, []) ->
Topic;
match_rule(Topic, [{rewrite, Filter, MP, Dest} | Rules]) ->
case emqx_topic:match(Topic, Filter) of
true -> match_regx(Topic, MP, Dest);
false -> match_rule(Topic, Rules)
end.
match_regx(Topic, MP, Dest) ->
case re:run(Topic, MP, [{capture, all_but_first, list}]) of
{match, Captured} ->
Vars = lists:zip(["\\$" ++ integer_to_list(I)
|| I <- lists:seq(1, length(Captured))], Captured),
iolist_to_binary(lists:foldl(
fun({Var, Val}, Acc) ->
re:replace(Acc, Var, Val, [global])
end, Dest, Vars));
nomatch ->
Topic
end.
compile(Rules) ->
lists:map(fun({rewrite, Topic, Re, Dest}) ->
{ok, MP} = re:compile(Re),
{rewrite, Topic, MP, Dest}
end, Rules).

View File

@ -0,0 +1,61 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io)
%%
%% 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_mod_subscription).
-behaviour(emqx_gen_mod).
-include_lib("emqx.hrl").
-include_lib("emqx_mqtt.hrl").
-export([load/1, on_client_connected/3, unload/1]).
-define(TAB, ?MODULE).
%%--------------------------------------------------------------------
%% Load/Unload Hook
%%--------------------------------------------------------------------
load(Topics) ->
emqx:hook('client.connected', fun ?MODULE:on_client_connected/3, [Topics]).
on_client_connected(?CONNACK_ACCEPT, Client = #mqtt_client{client_id = ClientId,
client_pid = ClientPid,
username = Username}, Topics) ->
Replace = fun(Topic) -> rep(<<"%u">>, Username, rep(<<"%c">>, ClientId, Topic)) end,
TopicTable = [{Replace(Topic), Qos} || {Topic, Qos} <- Topics],
ClientPid ! {subscribe, TopicTable},
{ok, Client};
on_client_connected(_ConnAck, _Client, _State) ->
ok.
unload(_) ->
emqx:unhook('client.connected', fun ?MODULE:on_client_connected/3).
%%--------------------------------------------------------------------
%% Internal Functions
%%--------------------------------------------------------------------
rep(<<"%c">>, ClientId, Topic) ->
emqx_topic:feed_var(<<"%c">>, ClientId, Topic);
rep(<<"%u">>, undefined, Topic) ->
Topic;
rep(<<"%u">>, Username, Topic) ->
emqx_topic:feed_var(<<"%u">>, Username, Topic).

33
src/emqx_modules.erl Normal file
View File

@ -0,0 +1,33 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io)
%%
%% 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_modules).
-export([load/0, unload/0]).
load() ->
lists:foreach(
fun({Mod, Env}) ->
ok = Mod:load(Env),
io:format("Load ~s module successfully.~n", [Mod])
end, emqx:env(modules, [])).
unload() ->
lists:foreach(
fun({Mod, Env}) ->
Mod:unload(Env) end,
emqx:env(modules, [])).

View File

@ -16,8 +16,6 @@
-module(emqx_net). -module(emqx_net).
-author("Feng Lee <feng@emqtt.io>").
-include_lib("kernel/include/inet.hrl"). -include_lib("kernel/include/inet.hrl").
-export([tcp_name/3, tcp_host/1, getopts/2, setopts/2, getaddr/2, -export([tcp_name/3, tcp_host/1, getopts/2, setopts/2, getaddr/2,

View File

@ -1,5 +1,5 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) %% Copyright (c) 2013-2018 EMQ Enterprise, Inc.
%% %%
%% Licensed under the Apache License, Version 2.0 (the "License"); %% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %% you may not use this file except in compliance with the License.
@ -16,8 +16,6 @@
-module(emqx_pmon). -module(emqx_pmon).
-author("Feng Lee <feng@emqtt.io>").
-export([new/0, monitor/2, demonitor/2, erase/2]). -export([new/0, monitor/2, demonitor/2, erase/2]).
-type(pmon() :: {?MODULE, map()}). -type(pmon() :: {?MODULE, map()}).

View File

@ -28,11 +28,14 @@
-boot_mnesia({mnesia, [boot]}). -boot_mnesia({mnesia, [boot]}).
-copy_mnesia({mnesia, [copy]}). -copy_mnesia({mnesia, [copy]}).
-export([start_link/0, topics/0, local_topics/0]). -export([start_link/1]).
%% For eunit tests %% For eunit tests
-export([start/0, stop/0]). -export([start/0, stop/0]).
%% Topics
-export([topics/0, local_topics/0]).
%% Route APIs %% Route APIs
-export([add_route/1, get_routes/1, del_route/1, has_route/1]). -export([add_route/1, get_routes/1, del_route/1, has_route/1]).
@ -49,7 +52,7 @@
-export([dump/0]). -export([dump/0]).
-record(state, {stats_timer}). -record(state, {stats_fun, stats_timer}).
-define(ROUTER, ?MODULE). -define(ROUTER, ?MODULE).
@ -60,21 +63,21 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
mnesia(boot) -> mnesia(boot) ->
ok = ekka_mnesia:create_table(mqtt_route, [ ok = ekka_mnesia:create_table(route, [
{type, bag}, {type, bag},
{ram_copies, [node()]}, {ram_copies, [node()]},
{record_name, mqtt_route}, {record_name, route},
{attributes, record_info(fields, mqtt_route)}]); {attributes, record_info(fields, route)}]);
mnesia(copy) -> mnesia(copy) ->
ok = ekka_mnesia:copy_table(mqtt_route, ram_copies). ok = ekka_mnesia:copy_table(route, ram_copies).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Start the Router %% Start the Router
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_link() -> start_link(StatsFun) ->
gen_server:start_link({local, ?ROUTER}, ?MODULE, [], []). gen_server:start_link({local, ?ROUTER}, ?MODULE, [StatsFun], []).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Topics %% Topics
@ -82,39 +85,39 @@ start_link() ->
-spec(topics() -> list(binary())). -spec(topics() -> list(binary())).
topics() -> topics() ->
mnesia:dirty_all_keys(mqtt_route). mnesia:dirty_all_keys(route).
-spec(local_topics() -> list(binary())). -spec(local_topics() -> list(binary())).
local_topics() -> local_topics() ->
ets:select(mqtt_local_route, [{{'$1', '_'}, [], ['$1']}]). ets:select(local_route, [{{'$1', '_'}, [], ['$1']}]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Match API %% Match API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Match Routes. %% @doc Match Routes.
-spec(match(Topic:: binary()) -> [mqtt_route()]). -spec(match(Topic:: binary()) -> [route()]).
match(Topic) when is_binary(Topic) -> match(Topic) when is_binary(Topic) ->
%% Optimize: ets??? %% Optimize: ets???
Matched = mnesia:ets(fun emqx_trie:match/1, [Topic]), Matched = mnesia:ets(fun emqx_trie:match/1, [Topic]),
%% Optimize: route table will be replicated to all nodes. %% Optimize: route table will be replicated to all nodes.
lists:append([ets:lookup(mqtt_route, To) || To <- [Topic | Matched]]). lists:append([ets:lookup(route, To) || To <- [Topic | Matched]]).
%% @doc Print Routes. %% @doc Print Routes.
-spec(print(Topic :: binary()) -> [ok]). -spec(print(Topic :: binary()) -> [ok]).
print(Topic) -> print(Topic) ->
[io:format("~s -> ~s~n", [To, Node]) || [io:format("~s -> ~s~n", [To, Node]) ||
#mqtt_route{topic = To, node = Node} <- match(Topic)]. #route{topic = To, node = Node} <- match(Topic)].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Route Management API %% Route Management API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Add Route. %% @doc Add Route.
-spec(add_route(binary() | mqtt_route()) -> ok | {error, Reason :: term()}). -spec(add_route(binary() | route()) -> ok | {error, Reason :: term()}).
add_route(Topic) when is_binary(Topic) -> add_route(Topic) when is_binary(Topic) ->
add_route(#mqtt_route{topic = Topic, node = node()}); add_route(#route{topic = Topic, node = node()});
add_route(Route = #mqtt_route{topic = Topic}) -> add_route(Route = #route{topic = Topic}) ->
case emqx_topic:wildcard(Topic) of case emqx_topic:wildcard(Topic) of
true -> case mnesia:is_transaction() of true -> case mnesia:is_transaction() of
true -> add_trie_route(Route); true -> add_trie_route(Route);
@ -126,23 +129,23 @@ add_route(Route = #mqtt_route{topic = Topic}) ->
add_direct_route(Route) -> add_direct_route(Route) ->
mnesia:async_dirty(fun mnesia:write/1, [Route]). mnesia:async_dirty(fun mnesia:write/1, [Route]).
add_trie_route(Route = #mqtt_route{topic = Topic}) -> add_trie_route(Route = #route{topic = Topic}) ->
case mnesia:wread({mqtt_route, Topic}) of case mnesia:wread({route, Topic}) of
[] -> emqx_trie:insert(Topic); [] -> emqx_trie:insert(Topic);
_ -> ok _ -> ok
end, end,
mnesia:write(Route). mnesia:write(Route).
%% @doc Lookup Routes %% @doc Lookup Routes
-spec(get_routes(binary()) -> [mqtt_route()]). -spec(get_routes(binary()) -> [route()]).
get_routes(Topic) -> get_routes(Topic) ->
ets:lookup(mqtt_route, Topic). ets:lookup(route, Topic).
%% @doc Delete Route %% @doc Delete Route
-spec(del_route(binary() | mqtt_route()) -> ok | {error, Reason :: term()}). -spec(del_route(binary() | route()) -> ok | {error, Reason :: term()}).
del_route(Topic) when is_binary(Topic) -> del_route(Topic) when is_binary(Topic) ->
del_route(#mqtt_route{topic = Topic, node = node()}); del_route(#route{topic = Topic, node = node()});
del_route(Route = #mqtt_route{topic = Topic}) -> del_route(Route = #route{topic = Topic}) ->
case emqx_topic:wildcard(Topic) of case emqx_topic:wildcard(Topic) of
true -> case mnesia:is_transaction() of true -> case mnesia:is_transaction() of
true -> del_trie_route(Route); true -> del_trie_route(Route);
@ -154,8 +157,8 @@ del_route(Route = #mqtt_route{topic = Topic}) ->
del_direct_route(Route) -> del_direct_route(Route) ->
mnesia:async_dirty(fun mnesia:delete_object/1, [Route]). mnesia:async_dirty(fun mnesia:delete_object/1, [Route]).
del_trie_route(Route = #mqtt_route{topic = Topic}) -> del_trie_route(Route = #route{topic = Topic}) ->
case mnesia:wread({mqtt_route, Topic}) of case mnesia:wread({route, Topic}) of
[Route] -> %% Remove route and trie [Route] -> %% Remove route and trie
mnesia:delete_object(Route), mnesia:delete_object(Route),
emqx_trie:delete(Topic); emqx_trie:delete(Topic);
@ -167,7 +170,7 @@ del_trie_route(Route = #mqtt_route{topic = Topic}) ->
%% @doc Has route? %% @doc Has route?
-spec(has_route(binary()) -> boolean()). -spec(has_route(binary()) -> boolean()).
has_route(Topic) when is_binary(Topic) -> has_route(Topic) when is_binary(Topic) ->
ets:member(mqtt_route, Topic). ets:member(route, Topic).
%% @private %% @private
-spec(trans(function(), list(any())) -> ok | {error, term()}). -spec(trans(function(), list(any())) -> ok | {error, term()}).
@ -183,7 +186,7 @@ trans(Fun, Args) ->
-spec(get_local_routes() -> list({binary(), node()})). -spec(get_local_routes() -> list({binary(), node()})).
get_local_routes() -> get_local_routes() ->
ets:tab2list(mqtt_local_route). ets:tab2list(local_route).
-spec(add_local_route(binary()) -> ok). -spec(add_local_route(binary()) -> ok).
add_local_route(Topic) -> add_local_route(Topic) ->
@ -195,15 +198,15 @@ del_local_route(Topic) ->
-spec(match_local(binary()) -> [mqtt_route()]). -spec(match_local(binary()) -> [mqtt_route()]).
match_local(Name) -> match_local(Name) ->
case ets:info(mqtt_local_route, size) of case ets:info(local_route, size) of
0 -> []; 0 -> [];
_ -> ets:foldl( _ -> ets:foldl(
fun({Filter, Node}, Matched) -> fun({Filter, Node}, Matched) ->
case emqx_topic:match(Name, Filter) of case emqx_topic:match(Name, Filter) of
true -> [#mqtt_route{topic = {local, Filter}, node = Node} | Matched]; true -> [#route{topic = {local, Filter}, node = Node} | Matched];
false -> Matched false -> Matched
end end
end, [], mqtt_local_route) end, [], local_route)
end. end.
-spec(clean_local_routes() -> ok). -spec(clean_local_routes() -> ok).
@ -211,7 +214,7 @@ clean_local_routes() ->
gen_server:call(?ROUTER, clean_local_routes). gen_server:call(?ROUTER, clean_local_routes).
dump() -> dump() ->
[{route, ets:tab2list(mqtt_route)}, {local_route, ets:tab2list(mqtt_local_route)}]. [{route, ets:tab2list(route)}, {local_route, ets:tab2list(local_route)}].
%% For unit test. %% For unit test.
start() -> start() ->
@ -224,23 +227,23 @@ stop() ->
%% gen_server Callbacks %% gen_server Callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([]) -> init([StatsFun]) ->
ekka:monitor(membership), ekka:monitor(membership),
ets:new(mqtt_local_route, [set, named_table, protected]), ets:new(local_route, [set, named_table, protected]),
{ok, TRef} = timer:send_interval(timer:seconds(1), stats), {ok, TRef} = timer:send_interval(timer:seconds(1), stats),
{ok, #state{stats_timer = TRef}}. {ok, #state{stats_fun = StatsFun, stats_timer = TRef}}.
handle_call({add_local_route, Topic}, _From, State) -> handle_call({add_local_route, Topic}, _From, State) ->
%% why node()...? %% why node()...?
ets:insert(mqtt_local_route, {Topic, node()}), ets:insert(local_route, {Topic, node()}),
{reply, ok, State}; {reply, ok, State};
handle_call({del_local_route, Topic}, _From, State) -> handle_call({del_local_route, Topic}, _From, State) ->
ets:delete(mqtt_local_route, Topic), ets:delete(local_route, Topic),
{reply, ok, State}; {reply, ok, State};
handle_call(clean_local_routes, _From, State) -> handle_call(clean_local_routes, _From, State) ->
ets:delete_all_objects(mqtt_local_route), ets:delete_all_objects(local_route),
{reply, ok, State}; {reply, ok, State};
handle_call(stop, _From, State) -> handle_call(stop, _From, State) ->
@ -253,19 +256,15 @@ handle_cast(_Msg, State) ->
{noreply, State}. {noreply, State}.
handle_info({membership, {mnesia, down, Node}}, State) -> handle_info({membership, {mnesia, down, Node}}, State) ->
global:trans({?LOCK, self()}, global:trans({?LOCK, self()}, fun() -> clean_routes_(Node) end),
fun() -> handle_info(stats, State);
clean_routes_(Node),
update_stats_()
end),
{noreply, State, hibernate};
handle_info({membership, _Event}, State) -> handle_info({membership, _Event}, State) ->
%% ignore %% ignore
{noreply, State}; {noreply, State};
handle_info(stats, State) -> handle_info(stats, State = #state{stats_fun = StatsFun}) ->
update_stats_(), StatsFun(mnesia:table_info(route, size)),
{noreply, State, hibernate}; {noreply, State, hibernate};
handle_info(_Info, State) -> handle_info(_Info, State) ->
@ -282,15 +281,12 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal Functions %% Internal Functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Clean Routes on Node %% Clean routes on the down node.
clean_routes_(Node) -> clean_routes_(Node) ->
Pattern = #mqtt_route{_ = '_', node = Node}, Pattern = #route{_ = '_', node = Node},
Clean = fun() -> Clean = fun() ->
[mnesia:delete_object(mqtt_route, R, write) || [mnesia:delete_object(route, R, write) ||
R <- mnesia:match_object(mqtt_route, Pattern, write)] R <- mnesia:match_object(route, Pattern, write)]
end, end,
mnesia:transaction(Clean). mnesia:transaction(Clean).
update_stats_() ->
emqx_stats:setstats('routes/count', 'routes/max', mnesia:table_info(mqtt_route, size)).

38
src/emqx_router_sup.erl Normal file
View File

@ -0,0 +1,38 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc.
%%
%% 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_router_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
StatsFun = emqx_stats:statsfun('routes/count', 'routes/max'),
SupFlags = #{strategy => one_for_all, intensity => 1, period => 5},
Router = #{id => emqx_router,
start => {emqx_router, start_link, [StatsFun]},
restart => permanent,
shutdown => 30000,
type => worker,
modules => [emqx_router]},
{ok, {SupFlags, [Router]}}.

View File

@ -1,5 +1,5 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc. (http://emqtt.io) %% Copyright (c) 2013-2018 EMQ Enterprise, Inc.
%% %%
%% Licensed under the Apache License, Version 2.0 (the "License"); %% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License. %% you may not use this file except in compliance with the License.
@ -16,8 +16,6 @@
-module(emqx_time). -module(emqx_time).
-author("Feng Lee <feng@emqtt.io>").
-export([seed/0, now_secs/0, now_secs/1, now_ms/0, now_ms/1, ts_from_ms/1]). -export([seed/0, now_secs/0, now_secs/1, now_ms/0, now_ms/1, ts_from_ms/1]).
seed() -> seed() ->

View File

@ -16,12 +16,8 @@
-module(emqx_topic). -module(emqx_topic).
-author("Feng Lee <feng@emqtt.io>").
-include("emqx_mqtt.hrl"). -include("emqx_mqtt.hrl").
-include("emqx_internal.hrl").
-import(lists, [reverse/1]). -import(lists, [reverse/1]).
-export([match/2, validate/1, triples/1, words/1, wildcard/1]). -export([match/2, validate/1, triples/1, words/1, wildcard/1]).
@ -206,8 +202,14 @@ parse(Topic, Options) ->
{Topic, Options}. {Topic, Options}.
if_not_contain(Key, Options, Fun) when Key == local; Key == fastlane -> if_not_contain(Key, Options, Fun) when Key == local; Key == fastlane ->
?IF(lists:member(Key, Options), error(invalid_topic), Fun()); case lists:member(Key, Options) of
true -> error(invalid_topic);
false -> Fun()
end;
if_not_contain(share, Options, Fun) -> if_not_contain(share, Options, Fun) ->
?IF(lists:keyfind(share, 1, Options), error(invalid_topic), Fun()). case lists:keyfind(share, 1, Options) of
true -> error(invalid_topic);
false -> Fun()
end.

View File

@ -41,21 +41,21 @@
-spec(mnesia(boot | copy) -> ok). -spec(mnesia(boot | copy) -> ok).
mnesia(boot) -> mnesia(boot) ->
%% Trie Table %% Trie Table
ok = ekka_mnesia:create_table(mqtt_trie, [ ok = ekka_mnesia:create_table(trie, [
{ram_copies, [node()]}, {ram_copies, [node()]},
{record_name, trie}, {record_name, trie},
{attributes, record_info(fields, trie)}]), {attributes, record_info(fields, trie)}]),
%% Trie Node Table %% Trie Node Table
ok = ekka_mnesia:create_table(mqtt_trie_node, [ ok = ekka_mnesia:create_table(trie_node, [
{ram_copies, [node()]}, {ram_copies, [node()]},
{record_name, trie_node}, {record_name, trie_node},
{attributes, record_info(fields, trie_node)}]); {attributes, record_info(fields, trie_node)}]);
mnesia(copy) -> mnesia(copy) ->
%% Copy Trie Table %% Copy Trie Table
ok = ekka_mnesia:copy_table(mqtt_trie), ok = ekka_mnesia:copy_table(trie),
%% Copy Trie Node Table %% Copy Trie Node Table
ok = ekka_mnesia:copy_table(mqtt_trie_node). ok = ekka_mnesia:copy_table(trie_node).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Trie API %% Trie API
@ -64,7 +64,7 @@ mnesia(copy) ->
%% @doc Insert topic to trie %% @doc Insert topic to trie
-spec(insert(Topic :: binary()) -> ok). -spec(insert(Topic :: binary()) -> ok).
insert(Topic) when is_binary(Topic) -> insert(Topic) when is_binary(Topic) ->
case mnesia:read(mqtt_trie_node, Topic) of case mnesia:read(trie_node, Topic) of
[#trie_node{topic = Topic}] -> [#trie_node{topic = Topic}] ->
ok; ok;
[TrieNode = #trie_node{topic = undefined}] -> [TrieNode = #trie_node{topic = undefined}] ->
@ -85,14 +85,14 @@ match(Topic) when is_binary(Topic) ->
%% @doc Lookup a Trie Node %% @doc Lookup a Trie Node
-spec(lookup(NodeId :: binary()) -> [#trie_node{}]). -spec(lookup(NodeId :: binary()) -> [#trie_node{}]).
lookup(NodeId) -> lookup(NodeId) ->
mnesia:read(mqtt_trie_node, NodeId). mnesia:read(trie_node, NodeId).
%% @doc Delete topic from trie %% @doc Delete topic from trie
-spec(delete(Topic :: binary()) -> ok). -spec(delete(Topic :: binary()) -> ok).
delete(Topic) when is_binary(Topic) -> delete(Topic) when is_binary(Topic) ->
case mnesia:read(mqtt_trie_node, Topic) of case mnesia:read(trie_node, Topic) of
[#trie_node{edge_count = 0}] -> [#trie_node{edge_count = 0}] ->
mnesia:delete({mqtt_trie_node, Topic}), mnesia:delete({trie_node, Topic}),
delete_path(lists:reverse(emqx_topic:triples(Topic))); delete_path(lists:reverse(emqx_topic:triples(Topic)));
[TrieNode] -> [TrieNode] ->
write_trie_node(TrieNode#trie_node{topic = undefined}); write_trie_node(TrieNode#trie_node{topic = undefined});
@ -108,9 +108,9 @@ delete(Topic) when is_binary(Topic) ->
%% @doc Add path to trie tree. %% @doc Add path to trie tree.
add_path({Node, Word, Child}) -> add_path({Node, Word, Child}) ->
Edge = #trie_edge{node_id = Node, word = Word}, Edge = #trie_edge{node_id = Node, word = Word},
case mnesia:read(mqtt_trie_node, Node) of case mnesia:read(trie_node, Node) of
[TrieNode = #trie_node{edge_count = Count}] -> [TrieNode = #trie_node{edge_count = Count}] ->
case mnesia:wread({mqtt_trie, Edge}) of case mnesia:wread({trie, Edge}) of
[] -> [] ->
write_trie_node(TrieNode#trie_node{edge_count = Count+1}), write_trie_node(TrieNode#trie_node{edge_count = Count+1}),
write_trie(#trie{edge = Edge, node_id = Child}); write_trie(#trie{edge = Edge, node_id = Child});
@ -131,11 +131,11 @@ match_node(NodeId, Words) ->
match_node(NodeId, Words, []). match_node(NodeId, Words, []).
match_node(NodeId, [], ResAcc) -> match_node(NodeId, [], ResAcc) ->
mnesia:read(mqtt_trie_node, NodeId) ++ 'match_#'(NodeId, ResAcc); mnesia:read(trie_node, NodeId) ++ 'match_#'(NodeId, ResAcc);
match_node(NodeId, [W|Words], ResAcc) -> match_node(NodeId, [W|Words], ResAcc) ->
lists:foldl(fun(WArg, Acc) -> lists:foldl(fun(WArg, Acc) ->
case mnesia:read(mqtt_trie, #trie_edge{node_id = NodeId, word = WArg}) of case mnesia:read(trie, #trie_edge{node_id = NodeId, word = WArg}) of
[#trie{node_id = ChildId}] -> match_node(ChildId, Words, Acc); [#trie{node_id = ChildId}] -> match_node(ChildId, Words, Acc);
[] -> Acc [] -> Acc
end end
@ -144,9 +144,9 @@ match_node(NodeId, [W|Words], ResAcc) ->
%% @private %% @private
%% @doc Match node with '#'. %% @doc Match node with '#'.
'match_#'(NodeId, ResAcc) -> 'match_#'(NodeId, ResAcc) ->
case mnesia:read(mqtt_trie, #trie_edge{node_id = NodeId, word = '#'}) of case mnesia:read(trie, #trie_edge{node_id = NodeId, word = '#'}) of
[#trie{node_id = ChildId}] -> [#trie{node_id = ChildId}] ->
mnesia:read(mqtt_trie_node, ChildId) ++ ResAcc; mnesia:read(trie_node, ChildId) ++ ResAcc;
[] -> [] ->
ResAcc ResAcc
end. end.
@ -156,10 +156,10 @@ match_node(NodeId, [W|Words], ResAcc) ->
delete_path([]) -> delete_path([]) ->
ok; ok;
delete_path([{NodeId, Word, _} | RestPath]) -> delete_path([{NodeId, Word, _} | RestPath]) ->
mnesia:delete({mqtt_trie, #trie_edge{node_id = NodeId, word = Word}}), mnesia:delete({trie, #trie_edge{node_id = NodeId, word = Word}}),
case mnesia:read(mqtt_trie_node, NodeId) of case mnesia:read(trie_node, NodeId) of
[#trie_node{edge_count = 1, topic = undefined}] -> [#trie_node{edge_count = 1, topic = undefined}] ->
mnesia:delete({mqtt_trie_node, NodeId}), mnesia:delete({trie_node, NodeId}),
delete_path(RestPath); delete_path(RestPath);
[TrieNode = #trie_node{edge_count = 1, topic = _}] -> [TrieNode = #trie_node{edge_count = 1, topic = _}] ->
write_trie_node(TrieNode#trie_node{edge_count = 0}); write_trie_node(TrieNode#trie_node{edge_count = 0});
@ -171,9 +171,9 @@ delete_path([{NodeId, Word, _} | RestPath]) ->
%% @private %% @private
write_trie(Trie) -> write_trie(Trie) ->
mnesia:write(mqtt_trie, Trie, write). mnesia:write(trie, Trie, write).
%% @private %% @private
write_trie_node(TrieNode) -> write_trie_node(TrieNode) ->
mnesia:write(mqtt_trie_node, TrieNode, write). mnesia:write(trie_node, TrieNode, write).

View File

@ -0,0 +1,35 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc.
%%
%% 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_base62_SUITE).
-include_lib("eunit/include/eunit.hrl").
-define(BASE62, emqx_base62).
-compile(export_all).
all() -> [t_base62_encode].
t_base62_encode(_) ->
10 = ?BASE62:decode(?BASE62:encode(10)),
100 = ?BASE62:decode(?BASE62:encode(100)),
9999 = ?BASE62:decode(?BASE62:encode(9999)),
65535 = ?BASE62:decode(?BASE62:encode(65535)),
<<X:128/unsigned-big-integer>> = emqx_guid:gen(),
<<Y:128/unsigned-big-integer>> = emqx_guid:gen(),
X = ?BASE62:decode(?BASE62:encode(X)),
Y = ?BASE62:decode(?BASE62:encode(Y)).

41
test/emqx_guid_SUITE.erl Normal file
View File

@ -0,0 +1,41 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc.
%%
%% 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_guid_SUITE).
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
all() -> [t_guid_gen, t_guid_hexstr, t_guid_base62].
t_guid_gen(_) ->
Guid1 = emqx_guid:gen(),
Guid2 = emqx_guid:gen(),
<<_:128>> = Guid1,
true = (Guid2 >= Guid1),
{Ts1, _, 0} = emqx_guid:new(),
Ts2 = emqx_guid:timestamp(emqx_guid:gen()),
true = Ts2 > Ts1.
t_guid_hexstr(_) ->
Guid = emqx_guid:gen(),
?assertEqual(Guid, emqx_guid:from_hexstr(emqx_guid:to_hexstr(Guid))).
t_guid_base62(_) ->
Guid = emqx_guid:gen(),
?assertEqual(Guid, emqx_guid:from_base62(emqx_guid:to_base62(Guid))).

View File

@ -16,8 +16,6 @@
-module(emqx_inflight_SUITE). -module(emqx_inflight_SUITE).
-author("Feng Lee <feng@emqtt.io>").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
%% CT %% CT

View File

@ -0,0 +1,43 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc.
%%
%% 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_keepalive_SUITE).
-compile(export_all).
all() -> [{group, keepalive}].
groups() -> [{keepalive, [], [t_keepalive]}].
%%--------------------------------------------------------------------
%% Keepalive
%%--------------------------------------------------------------------
t_keepalive(_) ->
{ok, KA} = emqx_keepalive:start(fun() -> {ok, 1} end, 1, {keepalive, timeout}),
[resumed, timeout] = lists:reverse(keepalive_recv(KA, [])).
keepalive_recv(KA, Acc) ->
receive
{keepalive, timeout} ->
case emqx_keepalive:check(KA) of
{ok, KA1} -> keepalive_recv(KA1, [resumed | Acc]);
{error, timeout} -> [timeout | Acc]
end
after 4000 ->
Acc
end.

46
test/emqx_misc_SUITE.erl Normal file
View File

@ -0,0 +1,46 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc.
%%
%% 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_misc_SUITE).
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
-define(SOCKOPTS, [binary,
{packet, raw},
{reuseaddr, true},
{backlog, 512},
{nodelay, true}]).
all() -> [t_merge_opts].
t_merge_opts(_) ->
Opts = emqx_misc:merge_opts(?SOCKOPTS, [raw,
binary,
{backlog, 1024},
{nodelay, false},
{max_clients, 1024},
{acceptors, 16}]),
?assertEqual(1024, proplists:get_value(backlog, Opts)),
?assertEqual(1024, proplists:get_value(max_clients, Opts)),
[binary, raw,
{acceptors, 16},
{backlog, 1024},
{max_clients, 1024},
{nodelay, false},
{packet, raw},
{reuseaddr, true}] = lists:sort(Opts).

View File

@ -0,0 +1,69 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc.
%%
%% 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_pqueue_SUITE).
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
-define(PQ, emqx_pqueue).
all() -> [t_priority_queue_plen, t_priority_queue_out2].
t_priority_queue_plen(_) ->
Q = ?PQ:new(),
0 = ?PQ:plen(0, Q),
Q0 = ?PQ:in(z, Q),
1 = ?PQ:plen(0, Q0),
Q1 = ?PQ:in(x, 1, Q0),
1 = ?PQ:plen(1, Q1),
Q2 = ?PQ:in(y, 2, Q1),
1 = ?PQ:plen(2, Q2),
Q3 = ?PQ:in(z, 2, Q2),
2 = ?PQ:plen(2, Q3),
{_, Q4} = ?PQ:out(1, Q3),
0 = ?PQ:plen(1, Q4),
{_, Q5} = ?PQ:out(Q4),
1 = ?PQ:plen(2, Q5),
{_, Q6} = ?PQ:out(Q5),
0 = ?PQ:plen(2, Q6),
1 = ?PQ:len(Q6),
{_, Q7} = ?PQ:out(Q6),
0 = ?PQ:len(Q7).
t_priority_queue_out2(_) ->
Els = [a, {b, 1}, {c, 1}, {d, 2}, {e, 2}, {f, 2}],
Q = ?PQ:new(),
Q0 = lists:foldl(
fun({El, P}, Acc) ->
?PQ:in(El, P, Acc);
(El, Acc) ->
?PQ:in(El, Acc)
end, Q, Els),
{Val, Q1} = ?PQ:out(Q0),
{value, d} = Val,
{Val1, Q2} = ?PQ:out(2, Q1),
{value, e} = Val1,
{Val2, Q3} = ?PQ:out(1, Q2),
{value, b} = Val2,
{Val3, Q4} = ?PQ:out(Q3),
{value, f} = Val3,
{Val4, Q5} = ?PQ:out(Q4),
{value, c} = Val4,
{Val5, Q6} = ?PQ:out(Q5),
{value, a} = Val5,
{empty, _Q7} = ?PQ:out(Q6).

View File

@ -16,12 +16,12 @@
-module(emqx_router_SUITE). -module(emqx_router_SUITE).
-compile(export_all).
-include("emqx.hrl"). -include("emqx.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-compile(export_all).
-define(R, emqx_router). -define(R, emqx_router).
all() -> all() ->
@ -35,7 +35,7 @@ groups() ->
t_match_route, t_match_route,
t_print, t_print,
t_has_route, t_has_route,
router_unused]}, t_unused]},
{local_route, [sequence], {local_route, [sequence],
[t_get_local_topics, [t_get_local_topics,
t_add_del_local_route, t_add_del_local_route,
@ -44,7 +44,7 @@ groups() ->
init_per_suite(Config) -> init_per_suite(Config) ->
ekka:start(), ekka:start(),
ekka_mnesia:ensure_started(), ekka_mnesia:ensure_started(),
{ok, _R} = emqx_router:start(), {ok, _} = emqx_router_sup:start_link(),
Config. Config.
end_per_suite(_Config) -> end_per_suite(_Config) ->
@ -81,10 +81,10 @@ t_match_route(_) ->
?R:add_route(<<"a/+/c">>), ?R:add_route(<<"a/+/c">>),
?R:add_route(<<"a/b/#">>), ?R:add_route(<<"a/b/#">>),
?R:add_route(<<"#">>), ?R:add_route(<<"#">>),
?assertEqual([#mqtt_route{topic = <<"#">>, node = Node}, ?assertEqual([#route{topic = <<"#">>, node = Node},
#mqtt_route{topic = <<"a/+/c">>, node = Node}, #route{topic = <<"a/+/c">>, node = Node},
#mqtt_route{topic = <<"a/b/#">>, node = Node}, #route{topic = <<"a/b/#">>, node = Node},
#mqtt_route{topic = <<"a/b/c">>, node = Node}], #route{topic = <<"a/b/c">>, node = Node}],
lists:sort(?R:match(<<"a/b/c">>))). lists:sort(?R:match(<<"a/b/c">>))).
t_has_route(_) -> t_has_route(_) ->
@ -119,12 +119,12 @@ t_match_local_route(_) ->
?R:add_local_route(<<"a/+/c">>), ?R:add_local_route(<<"a/+/c">>),
?R:add_local_route(<<"a/b/#">>), ?R:add_local_route(<<"a/b/#">>),
?R:add_local_route(<<"#">>), ?R:add_local_route(<<"#">>),
Matched = [Topic || #mqtt_route{topic = {local, Topic}} <- ?R:match_local(<<"a/b/c">>)], Matched = [Topic || #route{topic = {local, Topic}} <- ?R:match_local(<<"a/b/c">>)],
?assertEqual([<<"#">>, <<"a/+/c">>, <<"a/b/#">>, <<"a/b/c">>], lists:sort(Matched)). ?assertEqual([<<"#">>, <<"a/+/c">>, <<"a/b/#">>, <<"a/b/c">>], lists:sort(Matched)).
clear_tables() -> clear_tables() ->
?R:clean_local_routes(), ?R:clean_local_routes(),
lists:foreach(fun mnesia:clear_table/1, [mqtt_route, mqtt_trie, mqtt_trie_node]). lists:foreach(fun mnesia:clear_table/1, [route, trie, trie_node]).
router_add_del(_) -> router_add_del(_) ->
%% Add %% Add
@ -132,9 +132,9 @@ router_add_del(_) ->
?R:add_route(<<"a/b/c">>), ?R:add_route(<<"a/b/c">>),
?R:add_route(<<"+/#">>), ?R:add_route(<<"+/#">>),
Routes = [R1, R2 | _] = [ Routes = [R1, R2 | _] = [
#mqtt_route{topic = <<"#">>, node = node()}, #route{topic = <<"#">>, node = node()},
#mqtt_route{topic = <<"+/#">>, node = node()}, #route{topic = <<"+/#">>, node = node()},
#mqtt_route{topic = <<"a/b/c">>, node = node()}], #route{topic = <<"a/b/c">>, node = node()}],
Routes = lists:sort(?R:match(<<"a/b/c">>)), Routes = lists:sort(?R:match(<<"a/b/c">>)),
%% Batch Add %% Batch Add
@ -147,7 +147,7 @@ router_add_del(_) ->
{atomic, []} = mnesia:transaction(fun emqx_trie:lookup/1, [<<"a/b/c">>]), {atomic, []} = mnesia:transaction(fun emqx_trie:lookup/1, [<<"a/b/c">>]),
%% Batch Del %% Batch Del
R3 = #mqtt_route{topic = <<"#">>, node = 'a@127.0.0.1'}, R3 = #route{topic = <<"#">>, node = 'a@127.0.0.1'},
?R:add_route(R3), ?R:add_route(R3),
?R:del_route(R1), ?R:del_route(R1),
?R:del_route(R2), ?R:del_route(R2),
@ -155,16 +155,17 @@ router_add_del(_) ->
[] = lists:sort(?R:match(<<"a/b/c">>)). [] = lists:sort(?R:match(<<"a/b/c">>)).
t_print(_) -> t_print(_) ->
Routes = [#mqtt_route{topic = <<"a/b/c">>, node = node()}, Routes = [#route{topic = <<"a/b/c">>, node = node()},
#mqtt_route{topic = <<"#">>, node = node()}, #route{topic = <<"#">>, node = node()},
#mqtt_route{topic = <<"+/#">>, node = node()}], #route{topic = <<"+/#">>, node = node()}],
lists:foreach(fun(R) -> ?R:add_route(R) end, Routes), lists:foreach(fun(R) -> ?R:add_route(R) end, Routes),
?R:print(<<"a/b/c">>), ?R:print(<<"a/b/c">>),
?R:del_route(<<"+/#">>), ?R:del_route(<<"+/#">>),
?R:del_route(<<"a/b/c">>), ?R:del_route(<<"a/b/c">>),
?R:del_route(<<"#">>). ?R:del_route(<<"#">>).
router_unused(_) -> t_unused(_) ->
gen_server:call(emqx_router, bad_call), gen_server:call(?R, bad_call),
gen_server:cast(emqx_router, bad_msg), gen_server:cast(?R, bad_msg),
emqx_router ! bad_info. ?R ! bad_info.

28
test/emqx_time_SUITE.erl Normal file
View File

@ -0,0 +1,28 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2013-2018 EMQ Enterprise, Inc.
%%
%% 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_time_SUITE).
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
all() -> [t_time_now_to].
t_time_now_to(_) ->
emqx_time:seed(),
emqx_time:now_secs(),
emqx_time:now_ms().

View File

@ -133,5 +133,5 @@ t_delete3(_) ->
end). end).
clear_tables() -> clear_tables() ->
lists:foreach(fun mnesia:clear_table/1, [mqtt_trie, mqtt_trie_node]). lists:foreach(fun mnesia:clear_table/1, [trie, trie_node]).