Merge branch 'develop' of github.com:emqx/emqx into develop

This commit is contained in:
Feng Lee 2019-11-12 15:46:48 +08:00
commit d6ebbb7cce
86 changed files with 2162 additions and 1744 deletions

View File

@ -1,7 +1,7 @@
language: erlang
otp_release:
- 21.3
- 22.1
before_install:
- git clone https://github.com/erlang/rebar3.git; cd rebar3; ./bootstrap; sudo mv rebar3 /usr/local/bin/; cd ..

View File

@ -89,7 +89,7 @@ ct_setup:
.PHONY: ct
ct: ct_setup
@rebar3 ct -v --readable=false --name $(CT_NODE_NAME) --suite=$(shell echo $(foreach var,$(CT_SUITES),test/$(var)_SUITE) | tr ' ' ',')
@rebar3 ct -v --name $(CT_NODE_NAME) --suite=$(shell echo $(foreach var,$(CT_SUITES),test/$(var)_SUITE) | tr ' ' ',')
## Run one single CT with rebar3
## e.g. make ct-one-suite suite=emqx_bridge

View File

@ -7,6 +7,9 @@
[![Slack Invite](<https://slack-invite.emqx.io/badge.svg>)](https://slack-invite.emqx.io)
[![Twitter](https://img.shields.io/badge/Twiiter-EMQ%20X-1DA1F2?logo=twitter)](https://twitter.com/emqtt)
[![We are hiring](https://www.emqx.io/static/img/github_readme_cn_bg.png)](https://emqx.io/cn/about/jobs)
[English](./README.md) | 简体中文
*EMQ X* 是一款完全开源,高度可伸缩,高可用的分布式 MQTT 消息服务器,适用于 IoT、M2M 和移动应用程序,可处理千万级别的并发客户端。
@ -20,7 +23,13 @@
*EMQ X* 是跨平台的,支持 Linux、Unix、Mac OS 以及 Windows。这意味着 *EMQ X* 可以部署在 x86_64 架构的服务器上,也可以部署在 Raspberry Pi 这样的 ARM 设备上。
获取适合你的二进制软件包,[点此下载](https://emqx.io/downloads)。
**使用 EMQ X Docker 镜像安装**
```
docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 8883:8883 -p 8084:8084 -p 18083:18083 emqx/emqx
```
**或者 [点此下载](https://emqx.io/downloads) 适合你的二进制软件包**
- [单节点安装](https://docs.emqx.io/broker/v3/cn/install.html)
- [集群安装](https://docs.emqx.io/broker/v3/cn/cluster.html)

View File

@ -7,6 +7,9 @@
[![Slack Invite](<https://slack-invite.emqx.io/badge.svg>)](https://slack-invite.emqx.io)
[![Twitter](https://img.shields.io/badge/Twiiter-EMQ%20X-1DA1F2?logo=twitter)](https://twitter.com/emqtt)
[![We are hiring](https://www.emqx.io/static/img/github_readme_en_bg.png)](https://emqx.io/about/jobs)
English | [简体中文](./README-CN.md)
*EMQ X* broker is a fully open source, highly scalable, highly available distributed MQTT messaging broker for IoT, M2M and Mobile applications that can handle tens of millions of concurrent clients.
@ -20,7 +23,13 @@ Starting from 3.0 release, *EMQ X* broker fully supports MQTT V5.0 protocol spec
The *EMQ X* broker is cross-platform, which supports Linux, Unix, Mac OS and Windows. It means *EMQ X* can be deployed on x86_64 architecture servers and ARM devices like Raspberry Pi.
Download the binary package for your platform from [here](https://emqx.io/downloads).
**Installing via EMQ X Docker Image**
```
docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 8883:8883 -p 8084:8084 -p 18083:18083 emqx/emqx
```
**Or download the binary package for your platform from [here](https://emqx.io/downloads).**
- [Single Node Install](https://docs.emqx.io/broker/v3/en/install.html)
- [Multi Node Install](https://docs.emqx.io/broker/v3/en/cluster.html)

View File

@ -417,7 +417,7 @@ log.file = emqx.log
##
## Value: Number
## Default: 10M
## Supported Unit: KB | MB | G
## Supported Unit: KB | MB | GB
log.rotation.size = 10MB
## Maximum rotation count of log files.
@ -1349,9 +1349,14 @@ listener.ws.external.max_connections = 102400
## Value: Number
listener.ws.external.max_conn_rate = 1000
## Simulate the {active, N} option for the MQTT/WebSocket connections.
##
## Value: Number
listener.ws.external.active_n = 100
## Rate limit for the MQTT/WebSocket connections.
##
## Value: limit,duration
## Value: Limit,Duration
## Default: 100KB incoming per 10 seconds.
## listener.ws.external.rate_limit = 100KB,10s
@ -1557,9 +1562,14 @@ listener.wss.external.max_connections = 16
## Value: Number
listener.wss.external.max_conn_rate = 1000
## Simulate the {active, N} option for the MQTT/WebSocket/SSL connections.
##
## Value: Number
listener.wss.external.active_n = 100
## Rate limit for the MQTT/WebSocket/SSL connections.
##
## Value: limit,duration
## Value: Limit,Duration
## Default: 100KB incoming per 10 seconds.
## listener.wss.external.rate_limit = 100KB,10s

View File

@ -959,17 +959,18 @@ end}.
{force_gc_policy, GcPolicy};
("force_shutdown_policy", "default") ->
{DefaultLen, DefaultSize} =
case erlang:system_info(wordsize) of
case WordSize = erlang:system_info(wordsize) of
8 -> % arch_64
{8000, cuttlefish_bytesize:parse("800MB")};
4 -> % arch_32
{1000, cuttlefish_bytesize:parse("100MB")}
end,
{force_shutdown_policy, #{message_queue_len => DefaultLen,
max_heap_size => DefaultSize}};
max_heap_size => DefaultSize div WordSize
}};
("force_shutdown_policy", Val) ->
[Len, Siz] = string:tokens(Val, "| "),
MaxSiz = case erlang:system_info(wordsize) of
MaxSiz = case WordSize = erlang:system_info(wordsize) of
8 -> % arch_64
(1 bsl 59) - 1;
4 -> % arch_32
@ -983,7 +984,7 @@ end}.
cuttlefish:invalid(io_lib:format("force_shutdown_policy: heap-size ~s is too large", [Siz]));
Siz1 ->
#{message_queue_len => list_to_integer(Len),
max_heap_size => Siz1}
max_heap_size => Siz1 div WordSize}
end,
{force_shutdown_policy, ShutdownPolicy};
("mqueue_priorities", Val) ->
@ -1289,6 +1290,11 @@ end}.
{datatype, integer}
]}.
{mapping, "listener.ws.$name.active_n", "emqx.listeners", [
{default, 100},
{datatype, integer}
]}.
{mapping, "listener.ws.$name.zone", "emqx.listeners", [
{datatype, string}
]}.
@ -1442,6 +1448,11 @@ end}.
{datatype, integer}
]}.
{mapping, "listener.wss.$name.active_n", "emqx.listeners", [
{default, 100},
{datatype, integer}
]}.
{mapping, "listener.wss.$name.zone", "emqx.listeners", [
{datatype, string}
]}.

View File

@ -2,7 +2,7 @@
[{jsx, "2.9.0"}, % hex
{cowboy, "2.6.1"}, % hex
{gproc, "0.8.0"}, % hex
{esockd, {git, "https://github.com/emqx/esockd", {tag, "v5.5.1"}}},
{esockd, {git, "https://github.com/emqx/esockd", {tag, "v5.5.2"}}},
{ekka, {git, "https://github.com/emqx/ekka", {tag, "v0.6.2"}}},
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.4.1"}}},
{cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v3.0.0"}}}

View File

@ -18,21 +18,31 @@
-include("emqx.hrl").
-export([ get_acl_cache/2
-export([ list_acl_cache/0
, get_acl_cache/2
, put_acl_cache/3
, cleanup_acl_cache/0
, empty_acl_cache/0
, dump_acl_cache/0
, get_cache_size/0
, get_cache_max_size/0
, get_newest_key/0
, get_oldest_key/0
, cache_k/2
, cache_v/1
, get_cache_ttl/0
, is_enabled/0
]).
%% export for test
-export([ cache_k/2
, cache_v/1
, get_cache_size/0
, get_newest_key/0
, get_oldest_key/0
]).
-type(acl_result() :: allow | deny).
-type(system_time() :: integer()).
-type(cache_key() :: {emqx_types:pubsub(), emqx_types:topic()}).
-type(cache_val() :: {acl_result(), system_time()}).
-type(acl_cache_entry() :: {cache_key(), cache_val()}).
%% Wrappers for key and value
cache_k(PubSub, Topic)-> {PubSub, Topic}.
@ -42,8 +52,21 @@ cache_v(AclResult)-> {AclResult, time_now()}.
is_enabled() ->
application:get_env(emqx, enable_acl_cache, true).
%% We'll cleanup the cache before repalcing an expired acl.
-spec(get_acl_cache(publish | subscribe, emqx_topic:topic()) -> (acl_result() | not_found)).
-spec(get_cache_max_size() -> integer()).
get_cache_max_size() ->
application:get_env(emqx, acl_cache_max_size, 32).
-spec(get_cache_ttl() -> integer()).
get_cache_ttl() ->
application:get_env(emqx, acl_cache_ttl, 60000).
-spec(list_acl_cache() -> [acl_cache_entry()]).
list_acl_cache() ->
cleanup_acl_cache(),
map_acl_cache(fun(Cache) -> Cache end).
%% We'll cleanup the cache before replacing an expired acl.
-spec(get_acl_cache(emqx_types:pubsub(), emqx_topic:topic()) -> (acl_result() | not_found)).
get_acl_cache(PubSub, Topic) ->
case erlang:get(cache_k(PubSub, Topic)) of
undefined -> not_found;
@ -59,7 +82,7 @@ get_acl_cache(PubSub, Topic) ->
%% If the cache get full, and also the latest one
%% is expired, then delete all the cache entries
-spec(put_acl_cache(publish | subscribe, emqx_topic:topic(), acl_result()) -> ok).
-spec(put_acl_cache(emqx_types:pubsub(), emqx_topic:topic(), acl_result()) -> ok).
put_acl_cache(PubSub, Topic, AclResult) ->
MaxSize = get_cache_max_size(), true = (MaxSize =/= 0),
Size = get_cache_size(),
@ -97,7 +120,7 @@ evict_acl_cache() ->
erlang:erase(OldestK),
decr_cache_size().
%% cleanup all the exipired cache entries
%% cleanup all the expired cache entries
-spec(cleanup_acl_cache() -> ok).
cleanup_acl_cache() ->
keys_queue_set(
@ -108,9 +131,6 @@ get_oldest_key() ->
get_newest_key() ->
keys_queue_pick(queue_rear()).
get_cache_max_size() ->
application:get_env(emqx, acl_cache_max_size, 32).
get_cache_size() ->
case erlang:get(acl_cache_size) of
undefined -> 0;
@ -215,7 +235,7 @@ queue_rear() -> fun queue:get_r/1.
time_now() -> erlang:system_time(millisecond).
if_expired(CachedAt, Fun) ->
TTL = application:get_env(emqx, acl_cache_ttl, 60000),
TTL = get_cache_ttl(),
Now = time_now(),
if (CachedAt + TTL) =< Now ->
Fun(true);

View File

@ -158,7 +158,7 @@ encode_alarm({AlarmId, #alarm{severity = Severity,
{desc, [{severity, Severity},
{title, iolist_to_binary(Title)},
{summary, iolist_to_binary(Summary)},
{timestamp, emqx_time:now_ms(Ts)}]}]);
{timestamp, emqx_misc:now_to_secs(Ts)}]}]);
encode_alarm({AlarmId, undefined}) ->
emqx_json:safe_encode([{id, maybe_to_binary(AlarmId)}]);
encode_alarm({AlarmId, AlarmDesc}) ->

View File

@ -258,7 +258,7 @@ aggre(Routes) ->
-spec(forward(node(), emqx_types:topic(), emqx_types:delivery(), RPCMode::sync|async)
-> emqx_types:deliver_result()).
forward(Node, To, Delivery, async) ->
case emqx_rpc:cast(Node, ?BROKER, dispatch, [To, Delivery]) of
case emqx_rpc:cast(To, Node, ?BROKER, dispatch, [To, Delivery]) of
true -> ok;
{badrpc, Reason} ->
?LOG(error, "Ansync forward msg to ~s failed: ~p", [Node, Reason]),
@ -266,7 +266,7 @@ forward(Node, To, Delivery, async) ->
end;
forward(Node, To, Delivery, sync) ->
case emqx_rpc:call(Node, ?BROKER, dispatch, [To, Delivery]) of
case emqx_rpc:call(To, Node, ?BROKER, dispatch, [To, Delivery]) of
{badrpc, Reason} ->
?LOG(error, "Sync forward msg to ~s failed: ~p", [Node, Reason]),
{error, badrpc};

View File

@ -43,6 +43,11 @@
, code_change/3
]).
-ifdef(TEST).
-compile(export_all).
-compile(nowarn_export_all).
-endif.
-define(HELPER, ?MODULE).
-define(SUBID, emqx_subid).
-define(SUBMON, emqx_submon).

View File

@ -40,15 +40,11 @@
, handle_in/2
, handle_out/2
, handle_call/2
, handle_info/2
, handle_timeout/3
, handle_info/2
, terminate/2
]).
-export([ recvd/2
, sent/2
]).
%% export for ct
-export([set_field/3]).
@ -75,14 +71,10 @@
topic_aliases :: maybe(map()),
%% MQTT Topic Alias Maximum
alias_maximum :: maybe(map()),
%% Publish Stats
pub_stats :: emqx_types:stats(),
%% Timers
timers :: #{atom() => disabled | maybe(reference())},
%% Conn State
conn_state :: conn_state(),
%% GC State
gc_state :: maybe(emqx_gc:gc_state()),
%% Takeover
takeover :: boolean(),
%% Resume
@ -103,7 +95,6 @@
-type(output() :: emqx_types:packet() | action() | [action()]).
-define(TIMER_TABLE, #{
stats_timer => emit_stats,
alive_timer => keepalive,
retry_timer => retry_delivery,
await_timer => expire_awaiting_rel,
@ -113,8 +104,7 @@
-define(ATTR_KEYS, [conninfo, clientinfo, session, conn_state]).
-define(INFO_KEYS, ?ATTR_KEYS ++ [keepalive, will_msg, topic_aliases,
alias_maximum, gc_state]).
-define(INFO_KEYS, ?ATTR_KEYS ++ [keepalive, will_msg, topic_aliases, alias_maximum]).
%%--------------------------------------------------------------------
%% Info, Attrs and Caps
@ -146,12 +136,8 @@ info(will_msg, #channel{will_msg = undefined}) ->
undefined;
info(will_msg, #channel{will_msg = WillMsg}) ->
emqx_message:to_map(WillMsg);
info(pub_stats, #channel{pub_stats = PubStats}) ->
PubStats;
info(timers, #channel{timers = Timers}) ->
Timers;
info(gc_state, #channel{gc_state = GcState}) ->
maybe_apply(fun emqx_gc:info/1, GcState).
Timers.
%% @doc Get attrs of the channel.
-spec(attrs(channel()) -> emqx_types:attrs()).
@ -164,10 +150,8 @@ attrs(session, #channel{session = Session}) ->
attrs(Key, Channel) -> info(Key, Channel).
-spec(stats(channel()) -> emqx_types:stats()).
stats(#channel{pub_stats = PubStats, session = undefined}) ->
maps:to_list(PubStats);
stats(#channel{pub_stats = PubStats, session = Session}) ->
maps:to_list(PubStats) ++ emqx_session:stats(Session).
stats(#channel{session = Session})->
emqx_session:stats(Session).
-spec(caps(channel()) -> emqx_types:caps()).
caps(#channel{clientinfo = #{zone := Zone}}) ->
@ -194,7 +178,7 @@ init(ConnInfo = #{peername := {PeerHost, _Port}}, Options) ->
_ -> undefined
end,
Protocol = maps:get(protocol, ConnInfo, mqtt),
MountPoint = emqx_zone:get_env(Zone, mountpoint),
MountPoint = emqx_zone:mountpoint(Zone),
ClientInfo = #{zone => Zone,
protocol => Protocol,
peerhost => PeerHost,
@ -205,16 +189,10 @@ init(ConnInfo = #{peername := {PeerHost, _Port}}, Options) ->
is_bridge => false,
is_superuser => false
},
StatsTimer = case emqx_zone:enable_stats(Zone) of
true -> undefined;
false -> disabled
end,
#channel{conninfo = ConnInfo,
clientinfo = ClientInfo,
pub_stats = #{},
timers = #{stats_timer => StatsTimer},
timers = #{},
conn_state = idle,
gc_state = init_gc_state(Zone),
takeover = false,
resuming = false,
pendings = []
@ -223,17 +201,10 @@ init(ConnInfo = #{peername := {PeerHost, _Port}}, Options) ->
peer_cert_as_username(Options) ->
proplists:get_value(peer_cert_as_username, Options).
init_gc_state(Zone) ->
maybe_apply(fun emqx_gc:init/1, emqx_zone:force_gc_policy(Zone)).
%%--------------------------------------------------------------------
%% Handle incoming packet
%%--------------------------------------------------------------------
-spec(recvd(pos_integer(), channel()) -> channel()).
recvd(Bytes, Channel) ->
ensure_timer(stats_timer, maybe_gc_and_check_oom(Bytes, Channel)).
-spec(handle_in(emqx_types:packet(), channel())
-> {ok, channel()}
| {ok, output(), channel()}
@ -258,74 +229,69 @@ handle_in(?CONNECT_PACKET(ConnPkt), Channel) ->
end;
handle_in(Packet = ?PUBLISH_PACKET(_QoS), Channel) ->
NChannel = inc_pub_stats(publish_in, Channel),
case emqx_packet:check(Packet) of
ok -> handle_publish(Packet, NChannel);
ok -> handle_publish(Packet, Channel);
{error, ReasonCode} ->
handle_out(disconnect, ReasonCode, NChannel)
handle_out(disconnect, ReasonCode, Channel)
end;
handle_in(?PUBACK_PACKET(PacketId, _ReasonCode),
Channel = #channel{clientinfo = ClientInfo, session = Session}) ->
NChannel = inc_pub_stats(puback_in, Channel),
case emqx_session:puback(PacketId, Session) of
{ok, Msg, Publishes, NSession} ->
ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
handle_out({publish, Publishes}, NChannel#channel{session = NSession});
handle_out({publish, Publishes}, Channel#channel{session = NSession});
{ok, Msg, NSession} ->
ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
{ok, NChannel#channel{session = NSession}};
{ok, Channel#channel{session = NSession}};
{error, ?RC_PACKET_IDENTIFIER_IN_USE} ->
?LOG(warning, "The PUBACK PacketId ~w is inuse.", [PacketId]),
ok = emqx_metrics:inc('packets.puback.inuse'),
{ok, NChannel};
{ok, Channel};
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} ->
?LOG(warning, "The PUBACK PacketId ~w is not found", [PacketId]),
ok = emqx_metrics:inc('packets.puback.missed'),
{ok, NChannel}
{ok, Channel}
end;
handle_in(?PUBREC_PACKET(PacketId, _ReasonCode),
Channel = #channel{clientinfo = ClientInfo, session = Session}) ->
Channel1 = inc_pub_stats(pubrec_in, Channel),
case emqx_session:pubrec(PacketId, Session) of
{ok, Msg, NSession} ->
ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
NChannel = Channel1#channel{session = NSession},
NChannel = Channel#channel{session = NSession},
handle_out(pubrel, {PacketId, ?RC_SUCCESS}, NChannel);
{error, RC = ?RC_PACKET_IDENTIFIER_IN_USE} ->
?LOG(warning, "The PUBREC PacketId ~w is inuse.", [PacketId]),
ok = emqx_metrics:inc('packets.pubrec.inuse'),
handle_out(pubrel, {PacketId, RC}, Channel1);
handle_out(pubrel, {PacketId, RC}, Channel);
{error, RC = ?RC_PACKET_IDENTIFIER_NOT_FOUND} ->
?LOG(warning, "The PUBREC ~w is not found.", [PacketId]),
ok = emqx_metrics:inc('packets.pubrec.missed'),
handle_out(pubrel, {PacketId, RC}, Channel1)
handle_out(pubrel, {PacketId, RC}, Channel)
end;
handle_in(?PUBREL_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) ->
Channel1 = inc_pub_stats(pubrel_in, Channel),
case emqx_session:pubrel(PacketId, Session) of
{ok, NSession} ->
Channel2 = Channel1#channel{session = NSession},
handle_out(pubcomp, {PacketId, ?RC_SUCCESS}, Channel2);
NChannel = Channel#channel{session = NSession},
handle_out(pubcomp, {PacketId, ?RC_SUCCESS}, NChannel);
{error, NotFound} ->
ok = emqx_metrics:inc('packets.pubrel.missed'),
?LOG(warning, "The PUBREL PacketId ~w is not found", [PacketId]),
handle_out(pubcomp, {PacketId, NotFound}, Channel1)
handle_out(pubcomp, {PacketId, NotFound}, Channel)
end;
handle_in(?PUBCOMP_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) ->
Channel1 = inc_pub_stats(pubcomp_in, Channel),
case emqx_session:pubcomp(PacketId, Session) of
{ok, Publishes, NSession} ->
handle_out({publish, Publishes}, Channel1#channel{session = NSession});
handle_out({publish, Publishes}, Channel#channel{session = NSession});
{ok, NSession} ->
{ok, Channel1#channel{session = NSession}};
{ok, Channel#channel{session = NSession}};
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} ->
?LOG(warning, "The PUBCOMP PacketId ~w is not found", [PacketId]),
ok = emqx_metrics:inc('packets.pubcomp.missed'),
{ok, Channel1}
{ok, Channel}
end;
handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
@ -422,11 +388,6 @@ process_connect(ConnPkt = #mqtt_packet_connect{clean_start = CleanStart},
%% Process Publish
%%--------------------------------------------------------------------
inc_pub_stats(Key, Channel) -> inc_pub_stats(Key, 1, Channel).
inc_pub_stats(Key, I, Channel = #channel{pub_stats = PubStats}) ->
NPubStats = maps:update_with(Key, fun(V) -> V+I end, I, PubStats),
Channel#channel{pub_stats = NPubStats}.
handle_publish(Packet = ?PUBLISH_PACKET(_QoS, Topic, _PacketId),
Channel = #channel{conninfo = #{proto_ver := ProtoVer}}) ->
case pipeline([fun process_alias/2,
@ -540,10 +501,6 @@ do_unsubscribe(TopicFilter, _SubOpts, Channel =
%% Handle outgoing packet
%%--------------------------------------------------------------------
-spec(sent(pos_integer(), channel()) -> channel()).
sent(Bytes, Channel) ->
ensure_timer(stats_timer, maybe_gc_and_check_oom(Bytes, Channel)).
-spec(handle_out(term(), channel())
-> {ok, channel()}
| {ok, output(), channel()}
@ -578,8 +535,7 @@ handle_out({publish, Publishes}, Channel) when is_list(Publishes) ->
{ok, _Ch} -> Acc
end
end, [], Publishes),
NChannel = inc_pub_stats(publish_out, length(Packets), Channel),
{ok, {outgoing, lists:reverse(Packets)}, NChannel};
{ok, {outgoing, lists:reverse(Packets)}, Channel};
%% Ignore loop deliver
handle_out({publish, _PacketId, #message{from = ClientId,
@ -635,16 +591,16 @@ handle_out(connack, {ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnIn
shutdown(Reason, ?CONNACK_PACKET(ReasonCode1), Channel);
handle_out(puback, {PacketId, ReasonCode}, Channel) ->
{ok, ?PUBACK_PACKET(PacketId, ReasonCode), inc_pub_stats(puback_out, Channel)};
{ok, ?PUBACK_PACKET(PacketId, ReasonCode), Channel};
handle_out(pubrec, {PacketId, ReasonCode}, Channel) ->
{ok, ?PUBREC_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrec_out, Channel)};
{ok, ?PUBREC_PACKET(PacketId, ReasonCode), Channel};
handle_out(pubrel, {PacketId, ReasonCode}, Channel) ->
{ok, ?PUBREL_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrel_out, Channel)};
{ok, ?PUBREL_PACKET(PacketId, ReasonCode), Channel};
handle_out(pubcomp, {PacketId, ReasonCode}, Channel) ->
{ok, ?PUBCOMP_PACKET(PacketId, ReasonCode), inc_pub_stats(pubcomp_out, Channel)};
{ok, ?PUBCOMP_PACKET(PacketId, ReasonCode), Channel};
handle_out(suback, {PacketId, ReasonCodes},
Channel = #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) ->
@ -722,6 +678,9 @@ handle_call({takeover, 'end'}, Channel = #channel{session = Session,
AllPendings = lists:append(Delivers, Pendings),
shutdown(takeovered, AllPendings, Channel);
handle_call(list_acl_cache, Channel) ->
{reply, emqx_acl_cache:list_acl_cache(), Channel};
handle_call(Req, Channel) ->
?LOG(error, "Unexpected call: ~p", [Req]),
reply(ignored, Channel).
@ -747,11 +706,6 @@ handle_info({unsubscribe, TopicFilters}, Channel = #channel{clientinfo = ClientI
{_ReasonCodes, NChannel} = process_unsubscribe(TopicFilters1, Channel),
{ok, NChannel};
handle_info({register, Attrs, Stats}, #channel{clientinfo = #{clientid := ClientId}}) ->
ok = emqx_cm:register_channel(ClientId),
emqx_cm:set_chan_attrs(ClientId, Attrs),
emqx_cm:set_chan_stats(ClientId, Stats);
handle_info({sock_closed, _Reason}, Channel = #channel{conn_state = disconnected}) ->
{ok, Channel};
@ -775,6 +729,11 @@ handle_info({sock_closed, Reason}, Channel = #channel{conninfo = ConnInfo,
shutdown(Reason, Channel2)
end;
handle_info(clean_acl_cache, Channel) ->
?LOG(debug, "clear acl cache"),
ok = emqx_acl_cache:empty_acl_cache(),
{ok, Channel};
handle_info(Info, Channel) ->
?LOG(error, "Unexpected info: ~p~n", [Info]),
error(unexpected_info),
@ -788,12 +747,6 @@ handle_info(Info, Channel) ->
-> {ok, channel()}
| {ok, Result :: term(), channel()}
| {shutdown, Reason :: term(), channel()}).
handle_timeout(TRef, {emit_stats, Stats},
Channel = #channel{clientinfo = #{clientid := ClientId},
timers = #{stats_timer := TRef}}) ->
ok = emqx_cm:set_chan_stats(ClientId, Stats),
{ok, clean_timer(stats_timer, Channel)};
handle_timeout(TRef, {keepalive, StatVal},
Channel = #channel{keepalive = Keepalive,
timers = #{alive_timer := TRef}}) ->
@ -873,8 +826,6 @@ reset_timer(Name, Time, Channel) ->
clean_timer(Name, Channel = #channel{timers = Timers}) ->
Channel#channel{timers = maps:remove(Name, Timers)}.
interval(stats_timer, #channel{clientinfo = #{zone := Zone}}) ->
emqx_zone:get_env(Zone, idle_timeout, 30000);
interval(alive_timer, #channel{keepalive = KeepAlive}) ->
emqx_keepalive:info(interval, KeepAlive);
interval(retry_timer, #channel{session = Session}) ->
@ -912,6 +863,7 @@ publish_will_msg(undefined) ->
publish_will_msg(Msg) ->
emqx_broker:publish(Msg).
%% @doc Enrich MQTT Connect Info.
enrich_conninfo(#mqtt_packet_connect{
proto_name = ProtoName,
@ -1171,7 +1123,7 @@ ensure_keepalive(_AckProps, Channel = #channel{conninfo = ConnInfo}) ->
ensure_keepalive_timer(0, Channel) -> Channel;
ensure_keepalive_timer(Interval, Channel = #channel{clientinfo = #{zone := Zone}}) ->
Backoff = emqx_zone:get_env(Zone, keepalive_backoff, 0.75),
Backoff = emqx_zone:keepalive_backoff(Zone),
Keepalive = emqx_keepalive:init(round(timer:seconds(Interval) * Backoff)),
ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}).
@ -1197,19 +1149,6 @@ is_acl_enabled(#{zone := Zone, is_superuser := IsSuperuser}) ->
parse_topic_filters(TopicFilters) ->
lists:map(fun emqx_topic:parse/1, TopicFilters).
%%--------------------------------------------------------------------
%% Maybe GC and Check OOM
%%--------------------------------------------------------------------
maybe_gc_and_check_oom(_Oct, Channel = #channel{gc_state = undefined}) ->
Channel;
maybe_gc_and_check_oom(Oct, Channel = #channel{clientinfo = #{zone := Zone},
gc_state = GCSt}) ->
{IsGC, GCSt1} = emqx_gc:run(1, Oct, GCSt),
IsGC andalso emqx_metrics:inc('channel.gc.cnt'),
IsGC andalso emqx_zone:check_oom(Zone, fun(Shutdown) -> self() ! Shutdown end),
Channel#channel{gc_state = GCSt1}.
%%--------------------------------------------------------------------
%% Helper functions
%%--------------------------------------------------------------------

View File

@ -1,4 +1,4 @@
%%--------------------------------------------------------------------
%%-------------------------------------------------------------------
%% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
@ -93,7 +93,6 @@ start_link() ->
%%--------------------------------------------------------------------
%% @doc Register a channel.
%% Channel will be unregistered automatically when the channel process dies
-spec(register_channel(emqx_types:clientid()) -> ok).
register_channel(ClientId) when is_binary(ClientId) ->
register_channel(ClientId, self()).

View File

@ -14,7 +14,7 @@
%% limitations under the License.
%%--------------------------------------------------------------------
%% MQTT/TCP Connection
%% MQTT/TCP|TLS Connection
-module(emqx_connection).
-include("emqx.hrl").
@ -40,7 +40,7 @@
-export([call/2]).
%% callback
%% Callback
-export([init/4]).
%% Sys callbacks
@ -50,12 +50,15 @@
, system_get_state/1
]).
%% Internal callbacks
-export([wakeup_from_hib/2]).
%% Internal callback
-export([wakeup_from_hib/3]).
-import(emqx_misc,
[ maybe_apply/2
, start_timer/2
]).
-record(state, {
%% Parent
parent :: pid(),
%% TCP/TLS Transport
transport :: esockd:transport(),
%% TCP/TLS Socket
@ -64,34 +67,37 @@
peername :: emqx_types:peername(),
%% Sockname of the connection
sockname :: emqx_types:peername(),
%% Sock state
%% Sock State
sockstate :: emqx_types:sockstate(),
%% The {active, N} option
active_n :: pos_integer(),
%% Publish Limit
pub_limit :: maybe(esockd_rate_limit:bucket()),
%% Rate Limit
rate_limit :: maybe(esockd_rate_limit:bucket()),
%% Limiter
limiter :: maybe(emqx_limiter:limiter()),
%% Limit Timer
limit_timer :: maybe(reference()),
%% Parser State
%% Parse State
parse_state :: emqx_frame:parse_state(),
%% Serialize function
serialize :: emqx_frame:serialize_fun(),
%% Channel State
channel :: emqx_channel:channel(),
%% Idle timer
idle_timer :: reference()
%% GC State
gc_state :: maybe(emqx_gc:gc_state()),
%% Stats Timer
stats_timer :: disabled | maybe(reference()),
%% Idle Timer
idle_timer :: maybe(reference())
}).
-type(state() :: #state{}).
-define(ACTIVE_N, 100).
-define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n,
pub_limit, rate_limit]).
-define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n, limiter]).
-define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]).
-define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]).
-define(ENABLED(X), (X =/= undefined)).
-spec(start_link(esockd:transport(), esockd:socket(), proplists:proplist())
-> {ok, pid()}).
start_link(Transport, Socket, Options) ->
@ -123,13 +129,8 @@ info(sockstate, #state{sockstate = SockSt}) ->
SockSt;
info(active_n, #state{active_n = ActiveN}) ->
ActiveN;
info(pub_limit, #state{pub_limit = PubLimit}) ->
limit_info(PubLimit);
info(rate_limit, #state{rate_limit = RateLimit}) ->
limit_info(RateLimit).
limit_info(Limit) ->
emqx_misc:maybe_apply(fun esockd_rate_limit:info/1, Limit).
info(limiter, #state{limiter = Limiter}) ->
maybe_apply(fun emqx_limiter:info/1, Limiter).
%% @doc Get stats of the connection/channel.
-spec(stats(pid()|state()) -> emqx_types:stats()).
@ -147,6 +148,13 @@ stats(#state{transport = Transport,
ProcStats = emqx_misc:proc_stats(),
lists:append([SockStats, ConnStats, ChanStats, ProcStats]).
attrs(#state{active_n = ActiveN, sockstate = SockSt, channel = Channel}) ->
SockAttrs = #{active_n => ActiveN,
sockstate => SockSt
},
ChanAttrs = emqx_channel:attrs(Channel),
maps:merge(ChanAttrs, #{sockinfo => SockAttrs}).
call(Pid, Req) ->
gen_server:call(Pid, Req, infinity).
@ -169,7 +177,6 @@ init(Parent, Transport, RawSocket, Options) ->
do_init(Parent, Transport, Socket, Options) ->
{ok, Peername} = Transport:ensure_ok_or_exit(peername, [Socket]),
{ok, Sockname} = Transport:ensure_ok_or_exit(sockname, [Socket]),
emqx_logger:set_metadata_peername(esockd_net:format(Peername)),
Peercert = Transport:ensure_ok_or_exit(peercert, [Socket]),
ConnInfo = #{socktype => Transport:type(Socket),
peername => Peername,
@ -179,42 +186,39 @@ do_init(Parent, Transport, Socket, Options) ->
},
Zone = proplists:get_value(zone, Options),
ActiveN = proplists:get_value(active_n, Options, ?ACTIVE_N),
PubLimit = init_limiter(emqx_zone:get_env(Zone, publish_limit)),
RateLimit = init_limiter(proplists:get_value(rate_limit, Options)),
FrameOpts = emqx_zone:frame_options(Zone),
Limiter = emqx_limiter:init(Options),
FrameOpts = emqx_zone:mqtt_frame_options(Zone),
ParseState = emqx_frame:initial_parse_state(FrameOpts),
Serialize = emqx_frame:serialize_fun(),
Channel = emqx_channel:init(ConnInfo, Options),
IdleTimout = emqx_zone:get_env(Zone, idle_timeout, 30000),
IdleTimer = emqx_misc:start_timer(IdleTimout, idle_timeout),
HibAfterTimeout = emqx_zone:get_env(Zone, hibernate_after, IdleTimout*2),
State = #state{parent = Parent,
transport = Transport,
GcState = emqx_zone:init_gc_state(Zone),
StatsTimer = emqx_zone:stats_timer(Zone),
IdleTimeout = emqx_zone:idle_timeout(Zone),
IdleTimer = start_timer(IdleTimeout, idle_timeout),
emqx_misc:tune_heap_size(emqx_zone:oom_policy(Zone)),
emqx_logger:set_metadata_peername(esockd_net:format(Peername)),
State = #state{transport = Transport,
socket = Socket,
peername = Peername,
sockname = Sockname,
sockstate = idle,
active_n = ActiveN,
pub_limit = PubLimit,
rate_limit = RateLimit,
limiter = Limiter,
parse_state = ParseState,
serialize = Serialize,
channel = Channel,
gc_state = GcState,
stats_timer = StatsTimer,
idle_timer = IdleTimer
},
case activate_socket(State) of
{ok, NState} ->
hibernate(NState, #{hibernate_after => HibAfterTimeout});
hibernate(Parent, NState, #{idle_timeout => IdleTimeout});
{error, Reason} ->
ok = Transport:fast_close(Socket),
exit_on_sock_error(Reason)
end.
-compile({inline, [init_limiter/1]}).
init_limiter(undefined) -> undefined;
init_limiter({Rate, Burst}) ->
esockd_rate_limit:new(Rate, Burst).
exit_on_sock_error(Reason) when Reason =:= einval;
Reason =:= enotconn;
Reason =:= closed ->
@ -227,8 +231,7 @@ exit_on_sock_error(Reason) ->
%%--------------------------------------------------------------------
%% Recv Loop
recvloop(State = #state{parent = Parent},
Options = #{hibernate_after := HibAfterTimeout}) ->
recvloop(Parent, State, Options = #{idle_timeout := IdleTimeout}) ->
receive
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent,
@ -236,33 +239,49 @@ recvloop(State = #state{parent = Parent},
{'EXIT', Parent, Reason} ->
terminate(Reason, State);
Msg ->
process_msg([Msg], State, Options)
NState = ensure_stats_timer(IdleTimeout, State),
process_msg([Msg], Parent, NState, Options)
after
HibAfterTimeout ->
hibernate(State, Options)
IdleTimeout ->
NState = cancel_stats_timer(State),
hibernate(Parent, NState, Options)
end.
hibernate(State, Options) ->
proc_lib:hibernate(?MODULE, wakeup_from_hib, [State, Options]).
hibernate(Parent, State, Options) ->
proc_lib:hibernate(?MODULE, wakeup_from_hib, [Parent, State, Options]).
wakeup_from_hib(State, Options) ->
wakeup_from_hib(Parent, State, Options) ->
%% Maybe do something later here.
recvloop(State, Options).
recvloop(Parent, State, Options).
%%--------------------------------------------------------------------
%% Ensure/cancel stats timer
-compile({inline, [ensure_stats_timer/2]}).
ensure_stats_timer(Timeout, State = #state{stats_timer = undefined}) ->
State#state{stats_timer = start_timer(Timeout, emit_stats)};
ensure_stats_timer(_Timeout, State) -> State.
-compile({inline, [cancel_stats_timer/1]}).
cancel_stats_timer(State = #state{stats_timer = TRef}) when is_reference(TRef) ->
ok = emqx_misc:cancel_timer(TRef),
State#state{stats_timer = undefined};
cancel_stats_timer(State) -> State.
%%--------------------------------------------------------------------
%% Process next Msg
process_msg([], State, Options) ->
recvloop(State, Options);
process_msg([], Parent, State, Options) ->
recvloop(Parent, State, Options);
process_msg([Msg|More], State, Options) ->
process_msg([Msg|More], Parent, State, Options) ->
case catch handle_msg(Msg, State) of
ok ->
process_msg(More, State, Options);
process_msg(More, Parent, State, Options);
{ok, NState} ->
process_msg(More, NState, Options);
{ok, NextMsgs, NState} ->
process_msg(append_msg(NextMsgs, More), NState, Options);
process_msg(More, Parent, NState, Options);
{ok, Msgs, NState} ->
process_msg(append_msg(Msgs, More), Parent, NState, Options);
{stop, Reason} ->
terminate(Reason, State);
{stop, Reason, NState} ->
@ -284,14 +303,12 @@ handle_msg({'$gen_call', From, Req}, State) ->
stop(Reason, NState)
end;
handle_msg({Inet, _Sock, Data}, State = #state{channel = Channel})
when Inet == tcp; Inet == ssl ->
handle_msg({Inet, _Sock, Data}, State) when Inet == tcp; Inet == ssl ->
?LOG(debug, "RECV ~p", [Data]),
Oct = iolist_size(Data),
emqx_pd:update_counter(incoming_bytes, Oct),
emqx_pd:inc_counter(incoming_bytes, Oct),
ok = emqx_metrics:inc('bytes.received', Oct),
NChannel = emqx_channel:recvd(Oct, Channel),
parse_incoming(Data, State#state{channel = NChannel});
parse_incoming(Data, State);
handle_msg({incoming, Packet = ?CONNECT_PACKET(ConnPkt)},
State = #state{idle_timer = IdleTimer}) ->
@ -302,6 +319,9 @@ handle_msg({incoming, Packet = ?CONNECT_PACKET(ConnPkt)},
},
handle_incoming(Packet, NState);
handle_msg({incoming, ?PACKET(?PINGREQ)}, State) ->
handle_outgoing(?PACKET(?PINGRESP), State);
handle_msg({incoming, Packet}, State) ->
handle_incoming(Packet, State);
@ -315,30 +335,34 @@ handle_msg({Closed, _Sock}, State)
handle_msg({Passive, _Sock}, State)
when Passive == tcp_passive; Passive == ssl_passive ->
%% Rate limit here:)
NState = ensure_rate_limit(State),
handle_info(activate_socket, NState);
%% Rate limit timer expired.
handle_msg(activate_socket, State) ->
NState = State#state{sockstate = idle,
limit_timer = undefined
},
handle_info(activate_socket, NState);
InStats = #{cnt => emqx_pd:reset_counter(incoming_pubs),
oct => emqx_pd:reset_counter(incoming_bytes)
},
%% Ensure Rate Limit
NState = ensure_rate_limit(InStats, State),
%% Run GC and Check OOM
NState1 = check_oom(run_gc(InStats, NState)),
handle_info(activate_socket, NState1);
handle_msg(Deliver = {deliver, _Topic, _Msg},
State = #state{channel = Channel}) ->
Delivers = emqx_misc:drain_deliver([Deliver]),
Result = emqx_channel:handle_out(Delivers, Channel),
handle_return(Result, State);
Delivers = [Deliver|emqx_misc:drain_deliver()],
Ret = emqx_channel:handle_out(Delivers, Channel),
handle_chan_return(Ret, State);
handle_msg({outgoing, Packets}, State) ->
NState = handle_outgoing(Packets, State),
{ok, NState};
handle_outgoing(Packets, State);
%% something sent
handle_msg({inet_reply, _Sock, ok}, _State) ->
ok;
%% Something sent
handle_msg({inet_reply, _Sock, ok}, State = #state{active_n = ActiveN}) ->
case emqx_pd:get_counter(outgoing_pubs) > ActiveN of
true ->
OutStats = #{cnt => emqx_pd:reset_counter(outgoing_pubs),
oct => emqx_pd:reset_counter(outgoing_bytes)
},
{ok, check_oom(run_gc(OutStats, State))};
false -> ok
end;
handle_msg({inet_reply, _Sock, {error, Reason}}, State) ->
handle_info({sock_error, Reason}, State);
@ -349,7 +373,8 @@ handle_msg({timeout, TRef, TMsg}, State) ->
handle_msg(Shutdown = {shutdown, _Reason}, State) ->
stop(Shutdown, State);
handle_msg(Msg, State) -> handle_info(Msg, State).
handle_msg(Msg, State) ->
handle_info(Msg, State).
%%--------------------------------------------------------------------
%% Terminate
@ -363,8 +388,8 @@ terminate(Reason, State = #state{channel = Channel}) ->
%%--------------------------------------------------------------------
%% Sys callbacks
system_continue(_Parent, _Deb, {State, Options}) ->
recvloop(State, Options).
system_continue(Parent, _Deb, {State, Options}) ->
recvloop(Parent, State, Options).
system_terminate(Reason, _Parent, _Deb, {State, _}) ->
terminate(Reason, State).
@ -392,8 +417,8 @@ handle_call(_From, Req, State = #state{channel = Channel}) ->
shutdown(Reason, Reply, State#state{channel = NChannel});
{shutdown, Reason, Reply, OutPacket, NChannel} ->
NState = State#state{channel = NChannel},
NState1 = handle_outgoing(OutPacket, NState),
shutdown(Reason, Reply, NState1)
ok = handle_outgoing(OutPacket, NState),
shutdown(Reason, Reply, NState)
end.
%%--------------------------------------------------------------------
@ -402,8 +427,18 @@ handle_call(_From, Req, State = #state{channel = Channel}) ->
handle_timeout(TRef, idle_timeout, State = #state{idle_timer = TRef}) ->
shutdown(idle_timeout, State);
handle_timeout(TRef, emit_stats, State) ->
handle_timeout(TRef, {emit_stats, stats(State)}, State);
handle_timeout(TRef, limit_timeout, State = #state{limit_timer = TRef}) ->
NState = State#state{sockstate = idle,
limit_timer = undefined
},
handle_info(activate_socket, NState);
handle_timeout(TRef, emit_stats, State = #state{stats_timer = TRef,
channel = Channel}) ->
#{clientid := ClientId} = emqx_channel:info(clientinfo, Channel),
(ClientId =/= undefined) andalso
emqx_cm:set_chan_stats(ClientId, stats(State)),
{ok, State#state{stats_timer = undefined}};
handle_timeout(TRef, keepalive, State = #state{transport = Transport,
socket = Socket}) ->
@ -415,7 +450,8 @@ handle_timeout(TRef, keepalive, State = #state{transport = Transport,
end;
handle_timeout(TRef, Msg, State = #state{channel = Channel}) ->
handle_return(emqx_channel:handle_timeout(TRef, Msg, Channel), State).
Ret = emqx_channel:handle_timeout(TRef, Msg, Channel),
handle_chan_return(Ret, State).
%%--------------------------------------------------------------------
%% Parse incoming data
@ -450,30 +486,30 @@ next_incoming_msgs(Packets) ->
%%--------------------------------------------------------------------
%% Handle incoming packet
handle_incoming(Packet = ?PACKET(Type), State = #state{channel = Channel}) ->
_ = inc_incoming_stats(Type),
ok = emqx_metrics:inc_recv(Packet),
handle_incoming(Packet, State = #state{channel = Channel})
when is_record(Packet, mqtt_packet) ->
ok = inc_incoming_stats(Packet),
?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]),
handle_return(emqx_channel:handle_in(Packet, Channel), State);
handle_chan_return(emqx_channel:handle_in(Packet, Channel), State);
handle_incoming(FrameError, State = #state{channel = Channel}) ->
handle_return(emqx_channel:handle_in(FrameError, Channel), State).
handle_chan_return(emqx_channel:handle_in(FrameError, Channel), State).
%%--------------------------------------------------------------------
%% Handle channel return
handle_return(ok, State) ->
handle_chan_return(ok, State) ->
{ok, State};
handle_return({ok, NChannel}, State) ->
handle_chan_return({ok, NChannel}, State) ->
{ok, State#state{channel = NChannel}};
handle_return({ok, Replies, NChannel}, State) ->
handle_chan_return({ok, Replies, NChannel}, State) ->
{ok, next_msgs(Replies), State#state{channel = NChannel}};
handle_return({shutdown, Reason, NChannel}, State) ->
handle_chan_return({shutdown, Reason, NChannel}, State) ->
shutdown(Reason, State#state{channel = NChannel});
handle_return({shutdown, Reason, OutPacket, NChannel}, State) ->
handle_chan_return({shutdown, Reason, OutPacket, NChannel}, State) ->
NState = State#state{channel = NChannel},
NState1 = handle_outgoing(OutPacket, NState),
shutdown(Reason, NState1).
ok = handle_outgoing(OutPacket, NState),
shutdown(Reason, NState).
%%--------------------------------------------------------------------
%% Handle outgoing packets
@ -485,14 +521,13 @@ handle_outgoing(Packet, State) ->
send((serialize_and_inc_stats_fun(State))(Packet), State).
serialize_and_inc_stats_fun(#state{serialize = Serialize}) ->
fun(Packet = ?PACKET(Type)) ->
fun(Packet) ->
case Serialize(Packet) of
<<>> -> ?LOG(warning, "~s is discarded due to the frame is too large!",
[emqx_packet:format(Packet)]),
<<>>;
Data -> _ = inc_outgoing_stats(Type),
_ = emqx_metrics:inc_sent(Packet),
?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]),
Data -> ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]),
ok = inc_outgoing_stats(Packet),
Data
end
end.
@ -500,52 +535,52 @@ serialize_and_inc_stats_fun(#state{serialize = Serialize}) ->
%%--------------------------------------------------------------------
%% Send data
send(IoData, State = #state{transport = Transport,
socket = Socket,
channel = Channel}) ->
-spec(send(iodata(), state()) -> ok).
send(IoData, #state{transport = Transport, socket = Socket}) ->
Oct = iolist_size(IoData),
ok = emqx_metrics:inc('bytes.sent', Oct),
emqx_pd:inc_counter(outgoing_bytes, Oct),
case Transport:async_send(Socket, IoData) of
ok ->
State#state{channel = emqx_channel:sent(Oct, Channel)};
ok -> ok;
Error = {error, _Reason} ->
%% Simulate an inet_reply to postpone handling the error
self() ! {inet_reply, Socket, Error}, State
%% Send an inet_reply to postpone handling the error
self() ! {inet_reply, Socket, Error},
ok
end.
%%--------------------------------------------------------------------
%% Handle Info
handle_info({connack, ConnAck}, State = #state{active_n = ActiveN,
sockstate = SockSt,
channel = Channel}) ->
NState = handle_outgoing(ConnAck, State),
ChanAttrs = emqx_channel:attrs(Channel),
SockAttrs = #{active_n => ActiveN,
sockstate => SockSt
},
Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}),
handle_info({register, Attrs, stats(State)}, NState);
handle_info({connack, ConnAck}, State = #state{channel = Channel}) ->
#{clientid := ClientId} = emqx_channel:info(clientinfo, Channel),
ok = emqx_cm:register_channel(ClientId),
ok = emqx_cm:set_chan_attrs(ClientId, attrs(State)),
ok = emqx_cm:set_chan_stats(ClientId, stats(State)),
ok = handle_outgoing(ConnAck, State);
handle_info({enter, disconnected}, State = #state{active_n = ActiveN,
sockstate = SockSt,
channel = Channel}) ->
ChanAttrs = emqx_channel:attrs(Channel),
SockAttrs = #{active_n => ActiveN,
sockstate => SockSt
},
Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}),
handle_info({register, Attrs, stats(State)}, State);
handle_info({enter, disconnected}, State = #state{channel = Channel}) ->
#{clientid := ClientId} = emqx_channel:info(clientinfo, Channel),
emqx_cm:set_chan_attrs(ClientId, attrs(State)),
emqx_cm:set_chan_stats(ClientId, stats(State));
handle_info(activate_socket, State) ->
handle_info(activate_socket, State = #state{sockstate = OldSst}) ->
case activate_socket(State) of
{ok, NState} -> {ok, NState};
{ok, NState = #state{sockstate = NewSst}} ->
if OldSst =/= NewSst ->
{ok, {event, sockstate_changed}, NState};
true -> {ok, NState}
end;
{error, Reason} ->
handle_info({sock_error, Reason}, State)
end;
handle_info({event, sockstate_changed}, State = #state{channel = Channel}) ->
#{clientid := ClientId} = emqx_channel:info(clientinfo, Channel),
ClientId =/= undefined andalso emqx_cm:set_chan_attrs(ClientId, attrs(State));
%%TODO: this is not right
handle_info({sock_error, _Reason}, #state{sockstate = closed}) -> ok;
handle_info({sock_error, _Reason}, #state{sockstate = closed}) ->
ok;
handle_info({sock_error, Reason}, State) ->
?LOG(debug, "Socket error: ~p", [Reason]),
handle_info({sock_closed, Reason}, close_socket(State));
@ -560,7 +595,45 @@ handle_info({close, Reason}, State) ->
handle_info({sock_closed, Reason}, close_socket(State));
handle_info(Info, State = #state{channel = Channel}) ->
handle_return(emqx_channel:handle_info(Info, Channel), State).
handle_chan_return(emqx_channel:handle_info(Info, Channel), State).
%%--------------------------------------------------------------------
%% Ensure rate limit
ensure_rate_limit(Stats, State = #state{limiter = Limiter}) ->
case ?ENABLED(limiter) andalso emqx_limiter:check(Stats, Limiter) of
false -> State;
{ok, Limiter1} ->
State#state{limiter = Limiter1};
{pause, Time, Limiter1} ->
?LOG(debug, "Pause ~pms due to rate limit", [Time]),
TRef = start_timer(Time, limit_timeout),
State#state{sockstate = blocked,
limiter = Limiter1,
limit_timer = TRef
}
end.
%%--------------------------------------------------------------------
%% Run GC and Check OOM
run_gc(Stats, State = #state{gc_state = GcSt}) ->
case ?ENABLED(GcSt) andalso emqx_gc:run(Stats, GcSt) of
false -> State;
{IsGC, GcSt1} ->
IsGC andalso emqx_metrics:inc('channel.gc.cnt'),
State#state{gc_state = GcSt1}
end.
check_oom(State = #state{channel = Channel}) ->
#{zone := Zone} = emqx_channel:info(clientinfo, Channel),
OomPolicy = emqx_zone:oom_policy(Zone),
case ?ENABLED(OomPolicy) andalso emqx_misc:check_oom(OomPolicy) of
Shutdown = {shutdown, _Reason} ->
erlang:send(self(), Shutdown);
_Other -> ok
end,
State.
%%--------------------------------------------------------------------
%% Activate Socket
@ -587,48 +660,30 @@ close_socket(State = #state{transport = Transport, socket = Socket}) ->
ok = Transport:fast_close(Socket),
State#state{sockstate = closed}.
%%--------------------------------------------------------------------
%% Ensure rate limit
-define(ENABLED(Rl), (Rl =/= undefined)).
ensure_rate_limit(State = #state{rate_limit = Rl, pub_limit = Pl}) ->
Pubs = emqx_pd:reset_counter(incoming_pubs),
Bytes = emqx_pd:reset_counter(incoming_bytes),
Limiters = [{Pl, #state.pub_limit, Pubs} || ?ENABLED(Pl)] ++
[{Rl, #state.rate_limit, Bytes} || ?ENABLED(Rl)],
ensure_rate_limit(Limiters, State).
ensure_rate_limit([], State) ->
State;
ensure_rate_limit([{Rl, Pos, Cnt}|Limiters], State) ->
case esockd_rate_limit:check(Cnt, Rl) of
{0, Rl1} ->
ensure_rate_limit(Limiters, setelement(Pos, State, Rl1));
{Pause, Rl1} ->
?LOG(debug, "Pause ~pms due to rate limit", [Pause]),
TRef = erlang:send_after(Pause, self(), activate_socket),
NState = State#state{sockstate = blocked, limit_timer = TRef},
setelement(Pos, NState, Rl1)
end.
%%--------------------------------------------------------------------
%% Inc incoming/outgoing stats
-compile({inline, [inc_incoming_stats/1]}).
inc_incoming_stats(Type) when is_integer(Type) ->
emqx_pd:update_counter(recv_pkt, 1),
inc_incoming_stats(Packet = ?PACKET(Type)) ->
emqx_pd:inc_counter(recv_pkt, 1),
if
Type == ?PUBLISH ->
emqx_pd:update_counter(recv_msg, 1),
emqx_pd:update_counter(incoming_pubs, 1);
emqx_pd:inc_counter(recv_msg, 1),
emqx_pd:inc_counter(incoming_pubs, 1);
true -> ok
end.
end,
emqx_metrics:inc_recv(Packet).
-compile({inline, [inc_outgoing_stats/1]}).
inc_outgoing_stats(Type) ->
emqx_pd:update_counter(send_pkt, 1),
(Type == ?PUBLISH) andalso emqx_pd:update_counter(send_msg, 1).
inc_outgoing_stats(Packet = ?PACKET(Type)) ->
emqx_pd:inc_counter(send_pkt, 1),
if
Type == ?PUBLISH ->
emqx_pd:inc_counter(send_msg, 1),
emqx_pd:inc_counter(outgoing_pubs, 1);
true -> ok
end,
emqx_metrics:inc_sent(Packet).
%%--------------------------------------------------------------------
%% Helper functions
@ -646,13 +701,14 @@ next_msgs(Action) when is_tuple(Action) ->
next_msgs(Actions) when is_list(Actions) ->
Actions.
-compile({inline, [shutdown/2, shutdown/3]}).
shutdown(Reason, State) ->
stop({shutdown, Reason}, State).
shutdown(Reason, Reply, State) ->
stop({shutdown, Reason}, Reply, State).
-compile({inline, [stop/2]}).
-compile({inline, [stop/2, stop/3]}).
stop(Reason, State) ->
{stop, Reason, State}.

View File

@ -99,7 +99,7 @@ detect(#{clientid := ClientId, peerhost := PeerHost},
%% Create a flapping record.
Flapping = #flapping{clientid = ClientId,
peerhost = PeerHost,
started_at = emqx_time:now_ms(),
started_at = erlang:system_time(millisecond),
detect_cnt = 1
},
true = ets:insert(?FLAPPING_TAB, Flapping),
@ -111,7 +111,7 @@ detect(#{clientid := ClientId, peerhost := PeerHost},
get_policy() ->
emqx:get_env(flapping_detect_policy, ?DEFAULT_DETECT_POLICY).
now_diff(TS) -> emqx_time:now_ms() - TS.
now_diff(TS) -> erlang:system_time(millisecond) - TS.
%%--------------------------------------------------------------------
%% gen_server callbacks
@ -143,15 +143,15 @@ handle_cast({detected, Flapping = #flapping{clientid = ClientId,
[ClientId, esockd_net:ntoa(PeerHost), DetectCnt, Duration]),
%% Banned.
BannedFlapping = Flapping#flapping{clientid = {banned, ClientId},
banned_at = emqx_time:now_ms()
banned_at = erlang:system_time(millisecond)
},
alarm_handler:set_alarm({{flapping_detected, ClientId}, BannedFlapping}),
ets:insert(?FLAPPING_TAB, BannedFlapping);
false ->
?LOG(warning, "~s(~s) disconnected ~w times in ~wms",
[ClientId, esockd_net:ntoa(PeerHost), DetectCnt, Interval]),
ets:delete_object(?FLAPPING_TAB, Flapping)
[ClientId, esockd_net:ntoa(PeerHost), DetectCnt, Interval])
end,
ets:delete_object(?FLAPPING_TAB, Flapping),
{noreply, State};
handle_cast(Msg, State) ->
@ -160,7 +160,8 @@ handle_cast(Msg, State) ->
handle_info({timeout, TRef, expire_flapping}, State = #{tref := TRef}) ->
with_flapping_tab(fun expire_flapping/2,
[emqx_time:now_ms(), get_policy()]),
[erlang:system_time(millisecond),
get_policy()]),
{noreply, ensure_timer(State#{tref => undefined}), hibernate};
handle_info(Info, State) ->

View File

@ -29,6 +29,7 @@
-include("types.hrl").
-export([ init/1
, run/2
, run/3
, info/1
, reset/1
@ -57,21 +58,26 @@ init(#{count := Count, bytes := Bytes}) ->
?GCS(maps:from_list(Cnt ++ Oct)).
%% @doc Try to run GC based on reduntions of count or bytes.
-spec(run(#{cnt := pos_integer(), oct := pos_integer()}, gc_state())
-> {boolean(), gc_state()}).
run(#{cnt := Cnt, oct := Oct}, GcSt) ->
run(Cnt, Oct, GcSt).
-spec(run(pos_integer(), pos_integer(), gc_state())
-> {boolean(), gc_state()}).
run(Cnt, Oct, ?GCS(St)) ->
{Res, St1} = run([{cnt, Cnt}, {oct, Oct}], St),
{Res, St1} = do_run([{cnt, Cnt}, {oct, Oct}], St),
{Res, ?GCS(St1)}.
run([], St) ->
do_run([], St) ->
{false, St};
run([{K, N}|T], St) ->
do_run([{K, N}|T], St) ->
case dec(K, N, St) of
{true, St1} ->
true = erlang:garbage_collect(),
erlang:garbage_collect(),
{true, do_reset(St1)};
{false, St1} ->
run(T, St1)
do_run(T, St1)
end.
%% @doc Info of GC state.

73
src/emqx_limiter.erl Normal file
View File

@ -0,0 +1,73 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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_limiter).
-include("types.hrl").
-export([init/1, info/1, check/2]).
-import(emqx_misc, [maybe_apply/2]).
-record(limiter, {
%% Publish Limit
pub_limit :: maybe(esockd_rate_limit:bucket()),
%% Rate Limit
rate_limit :: maybe(esockd_rate_limit:bucket())
}).
-type(limiter() :: #limiter{}).
-export_type([limiter/0]).
-define(ENABLED(Rl), (Rl =/= undefined)).
-spec(init(proplists:proplist()) -> maybe(limiter())).
init(Options) ->
Zone = proplists:get_value(zone, Options),
Pl = emqx_zone:publish_limit(Zone),
Rl = proplists:get_value(rate_limit, Options),
case ?ENABLED(Pl) or ?ENABLED(Rl) of
true -> #limiter{pub_limit = init_limit(Pl),
rate_limit = init_limit(Rl)
};
false -> undefined
end.
init_limit(Rl) ->
maybe_apply(fun esockd_rate_limit:new/1, Rl).
info(#limiter{pub_limit = Pl, rate_limit = Rl}) ->
#{pub_limit => info(Pl), rate_limit => info(Rl)};
info(Rl) ->
maybe_apply(fun esockd_rate_limit:info/1, Rl).
check(#{cnt := Cnt, oct := Oct}, Limiter = #limiter{pub_limit = Pl,
rate_limit = Rl}) ->
do_check([{#limiter.pub_limit, Cnt, Pl} || ?ENABLED(Pl)] ++
[{#limiter.rate_limit, Oct, Rl} || ?ENABLED(Rl)], Limiter).
do_check([], Limiter) ->
{ok, Limiter};
do_check([{Pos, Cnt, Rl}|More], Limiter) ->
case esockd_rate_limit:check(Cnt, Rl) of
{0, Rl1} ->
do_check(More, setelement(Pos, Limiter, Rl1));
{Pause, Rl1} ->
{pause, Pause, setelement(Pos, Limiter, Rl1)}
end.

View File

@ -43,6 +43,7 @@
, set_primary_log_level/1
, set_log_handler_level/2
, set_log_level/1
, set_all_log_handlers_level/1
]).
-export([ get_primary_log_level/0
@ -181,12 +182,9 @@ log_hanlder_info(#{id := Id, level := Level, module := logger_std_h,
Type =:= standard_error ->
{Id, Level, console};
log_hanlder_info(#{id := Id, level := Level, module := logger_std_h,
config := #{type := Type}}) ->
case Type of
{file, Filename} -> {Id, Level, Filename};
{file, Filename, _Opts} -> {Id, Level, Filename};
_ -> {Id, Level, unknown}
end;
config := Config = #{type := file}}) ->
{Id, Level, maps:get(file, Config, atom_to_list(Id))};
log_hanlder_info(#{id := Id, level := Level, module := logger_disk_log_h,
config := #{file := Filename}}) ->
{Id, Level, Filename};

View File

@ -281,7 +281,7 @@ do_inc_recv(?PUBLISH_PACKET(QoS, _PktId)) ->
?QOS_0 -> inc('messages.qos0.received');
?QOS_1 -> inc('messages.qos1.received');
?QOS_2 -> inc('messages.qos2.received');
_ -> ignore
_ -> ok
end,
inc('packets.publish.received');
do_inc_recv(?PACKET(?PUBACK)) ->
@ -302,13 +302,12 @@ do_inc_recv(?PACKET(?DISCONNECT)) ->
inc('packets.disconnect.received');
do_inc_recv(?PACKET(?AUTH)) ->
inc('packets.auth.received');
do_inc_recv(_Packet) ->
ignore.
do_inc_recv(_Packet) -> ok.
%% @doc Inc packets sent. Will not count $SYS PUBLISH.
-spec(inc_sent(emqx_types:packet()) -> ok | ignore).
-spec(inc_sent(emqx_types:packet()) -> ok).
inc_sent(?PUBLISH_PACKET(_QoS, <<"$SYS/", _/binary>>, _, _)) ->
ignore;
ok;
inc_sent(Packet) ->
inc('packets.sent'),
do_inc_sent(Packet).
@ -349,8 +348,7 @@ do_inc_sent(?PACKET(?DISCONNECT)) ->
inc('packets.disconnect.sent');
do_inc_sent(?PACKET(?AUTH)) ->
inc('packets.auth.sent');
do_inc_sent(_Packet) ->
ignore.
do_inc_sent(_Packet) -> ok.
%%--------------------------------------------------------------------
%% gen_server callbacks
@ -368,7 +366,7 @@ init([]) ->
Metric = #metric{name = Name, type = Type, idx = reserved_idx(Name)},
true = ets:insert(?TAB, Metric),
ok = counters:put(CRef, Idx, 0)
end,?BYTES_METRICS ++ ?PACKET_METRICS ++ ?MESSAGE_METRICS ++ ?MQTT_METRICS),
end,?BYTES_METRICS ++ ?PACKET_METRICS ++ ?MESSAGE_METRICS ++ ?CHAN_METRICS ++ ?MQTT_METRICS),
{ok, #state{next_idx = ?RESERVED_IDX + 1}, hibernate}.
handle_call({create, Type, Name}, _From, State = #state{next_idx = ?MAX_SIZE}) ->

View File

@ -16,6 +16,8 @@
-module(emqx_misc).
-compile(inline).
-include("types.hrl").
-include("logger.hrl").
@ -26,22 +28,20 @@
, start_timer/2
, start_timer/3
, cancel_timer/1
, drain_deliver/0
, drain_down/1
, check_oom/1
, check_oom/2
, tune_heap_size/1
, proc_name/2
, proc_stats/0
, proc_stats/1
, rand_seed/0
, now_to_secs/1
, now_to_ms/1
, index_of/2
]).
-export([ drain_deliver/0
, drain_deliver/1
, drain_down/1
]).
-compile({inline,
[ start_timer/2
, start_timer/3
]}).
%% @doc Merge options
-spec(merge_opts(Opts, Opts) -> Opts when Opts :: proplists:proplist()).
merge_opts(Defaults, Options) ->
@ -101,7 +101,7 @@ start_timer(Interval, Msg) ->
-spec(start_timer(integer(), pid() | atom(), term()) -> reference()).
start_timer(Interval, Dest, Msg) ->
erlang:start_timer(Interval, Dest, Msg).
erlang:start_timer(erlang:ceil(Interval), Dest, Msg).
-spec(cancel_timer(maybe(reference())) -> ok).
cancel_timer(Timer) when is_reference(Timer) ->
@ -112,6 +112,68 @@ cancel_timer(Timer) when is_reference(Timer) ->
end;
cancel_timer(_) -> ok.
%% @doc Drain delivers from the channel proc's mailbox.
drain_deliver() ->
drain_deliver([]).
drain_deliver(Acc) ->
receive
Deliver = {deliver, _Topic, _Msg} ->
drain_deliver([Deliver|Acc])
after 0 ->
lists:reverse(Acc)
end.
%% @doc Drain process 'DOWN' events.
-spec(drain_down(pos_integer()) -> list(pid())).
drain_down(Cnt) when Cnt > 0 ->
drain_down(Cnt, []).
drain_down(0, Acc) ->
lists:reverse(Acc);
drain_down(Cnt, Acc) ->
receive
{'DOWN', _MRef, process, Pid, _Reason} ->
drain_down(Cnt - 1, [Pid|Acc])
after 0 ->
lists:reverse(Acc)
end.
%% @doc Check process's mailbox and heapsize against OOM policy,
%% return `ok | {shutdown, Reason}' accordingly.
%% `ok': There is nothing out of the ordinary.
%% `shutdown': Some numbers (message queue length hit the limit),
%% hence shutdown for greater good (system stability).
-spec(check_oom(emqx_types:oom_policy()) -> ok | {shutdown, term()}).
check_oom(Policy) ->
check_oom(self(), Policy).
-spec(check_oom(pid(), emqx_types:oom_policy()) -> ok | {shutdown, term()}).
check_oom(Pid, #{message_queue_len := MaxQLen,
max_heap_size := MaxHeapSize}) ->
case process_info(Pid, [message_queue_len, total_heap_size]) of
undefined -> ok;
[{message_queue_len, QLen}, {total_heap_size, HeapSize}] ->
do_check_oom([{QLen, MaxQLen, message_queue_too_long},
{HeapSize, MaxHeapSize, proc_heap_too_large}
])
end.
do_check_oom([]) -> ok;
do_check_oom([{Val, Max, Reason}|Rest]) ->
case is_integer(Max) andalso (0 < Max) andalso (Max < Val) of
true -> {shutdown, Reason};
false -> do_check_oom(Rest)
end.
tune_heap_size(#{max_heap_size := MaxHeapSize}) ->
%% If set to zero, the limit is disabled.
erlang:process_flag(max_heap_size, #{size => MaxHeapSize,
kill => false,
error_logger => true
});
tune_heap_size(undefined) -> ok.
-spec(proc_name(atom(), pos_integer()) -> atom()).
proc_name(Mod, Id) ->
list_to_atom(lists:concat([Mod, "_", Id])).
@ -132,32 +194,16 @@ proc_stats(Pid) ->
[{mailbox_len, Len}|ProcStats]
end.
%% @doc Drain delivers from the channel's mailbox.
drain_deliver() ->
drain_deliver([]).
rand_seed() ->
rand:seed(exsplus, erlang:timestamp()).
drain_deliver(Acc) ->
receive
Deliver = {deliver, _Topic, _Msg} ->
drain_deliver([Deliver|Acc])
after 0 ->
lists:reverse(Acc)
end.
-spec(now_to_secs(erlang:timestamp()) -> pos_integer()).
now_to_secs({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs.
%% @doc Drain process down events.
-spec(drain_down(pos_integer()) -> list(pid())).
drain_down(Cnt) when Cnt > 0 ->
drain_down(Cnt, []).
drain_down(0, Acc) ->
lists:reverse(Acc);
drain_down(Cnt, Acc) ->
receive
{'DOWN', _MRef, process, Pid, _Reason} ->
drain_down(Cnt - 1, [Pid|Acc])
after 0 ->
drain_down(0, Acc)
end.
-spec(now_to_ms(erlang:timestamp()) -> pos_integer()).
now_to_ms({MegaSecs, Secs, MicroSecs}) ->
(MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000).
%% lists:index_of/2
index_of(E, L) ->

View File

@ -62,7 +62,7 @@ on_client_connected(ClientInfo, ConnAck, ConnInfo, Env) ->
connack => ConnAck,
clean_start => CleanStart,
expiry_interval => ExpiryInterval,
ts => emqx_time:now_ms()
ts => erlang:system_time(millisecond)
},
case emqx_json:safe_encode(Presence) of
{ok, Payload} ->
@ -78,7 +78,7 @@ on_client_disconnected(ClientInfo, Reason, ConnInfo, Env) ->
Presence = #{clientid => ClientId,
username => Username,
reason => reason(Reason),
ts => emqx_time:now_ms()
ts => erlang:system_time(millisecond)
},
case emqx_json:safe_encode(Presence) of
{ok, Payload} ->

View File

@ -1,97 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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.
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
%% @doc OOM (Out Of Memory) monitor for the channel process.
%% @end
%%--------------------------------------------------------------------
-module(emqx_oom).
-include("types.hrl").
-export([ init/1
, check/1
, info/1
]).
-export_type([opts/0, oom_policy/0]).
-type(opts() :: #{message_queue_len => non_neg_integer(),
max_heap_size => non_neg_integer()
}).
-opaque(oom_policy() :: {oom_policy, opts()}).
-type(reason() :: message_queue_too_long|proc_heap_too_large).
-define(DISABLED, 0).
%% @doc Init the OOM policy.
-spec(init(opts()) -> oom_policy()).
init(#{message_queue_len := MaxQLen,
max_heap_size := MaxHeapSizeInBytes}) ->
MaxHeapSize = MaxHeapSizeInBytes div erlang:system_info(wordsize),
%% If set to zero, the limit is disabled.
_ = erlang:process_flag(max_heap_size, #{size => MaxHeapSize,
kill => false,
error_logger => true
}),
{oom_policy, #{message_queue_len => MaxQLen,
max_heap_size => MaxHeapSize
}}.
%% @doc Check self() process status against channel process management policy,
%% return `ok | {shutdown, Reason}' accordingly.
%% `ok': There is nothing out of the ordinary.
%% `shutdown': Some numbers (message queue length hit the limit),
%% hence shutdown for greater good (system stability).
-spec(check(oom_policy()) -> ok | {shutdown, reason()}).
check({oom_policy, #{message_queue_len := MaxQLen,
max_heap_size := MaxHeapSize}}) ->
Qlength = proc_info(message_queue_len),
HeapSize = proc_info(total_heap_size),
do_check([{fun() -> is_exceeded(Qlength, MaxQLen) end,
{shutdown, message_queue_too_long}},
{fun() -> is_exceeded(HeapSize, MaxHeapSize) end,
{shutdown, proc_heap_too_large}}]).
do_check([]) -> ok;
do_check([{Pred, Result} | Rest]) ->
case Pred() of
true -> Result;
false -> do_check(Rest)
end.
-spec(info(oom_policy()) -> opts()).
info({oom_policy, Opts}) -> Opts.
-compile({inline,
[ is_exceeded/2
, is_enabled/1
, proc_info/1
]}).
is_exceeded(Val, Max) ->
is_enabled(Max) andalso Val > Max.
is_enabled(Max) ->
is_integer(Max) andalso Max > ?DISABLED.
proc_info(Key) ->
{Key, Value} = erlang:process_info(self(), Key),
Value.

View File

@ -21,14 +21,14 @@
-export([ get_counters/1
, get_counter/1
, update_counter/2
, inc_counter/2
, reset_counter/1
]).
-compile({inline,
[ get_counters/1
, get_counter/1
, update_counter/2
, inc_counter/2
, reset_counter/1
]}).
@ -42,8 +42,8 @@ get_counters(Keys) when is_list(Keys) ->
get_counter(Key) ->
case get(Key) of undefined -> 0; Cnt -> Cnt end.
-spec(update_counter(key(), number()) -> maybe(number())).
update_counter(Key, Inc) ->
-spec(inc_counter(key(), number()) -> maybe(number())).
inc_counter(Key, Inc) ->
put(Key, get_counter(Key) + Inc).
-spec(reset_counter(key()) -> number()).

View File

@ -18,8 +18,11 @@
-module(emqx_rpc).
-export([ call/4
, call/5
, cast/4
, cast/5
, multicall/4
, multicall/5
]).
-compile({inline,
@ -34,15 +37,27 @@
call(Node, Mod, Fun, Args) ->
filter_result(?RPC:call(rpc_node(Node), Mod, Fun, Args)).
call(Key, Node, Mod, Fun, Args) ->
filter_result(?RPC:call(rpc_node({Key, Node}), Mod, Fun, Args)).
multicall(Nodes, Mod, Fun, Args) ->
filter_result(?RPC:multicall(rpc_nodes(Nodes), Mod, Fun, Args)).
multicall(Key, Nodes, Mod, Fun, Args) ->
filter_result(?RPC:multicall(rpc_nodes([{Key, Node} || Node <- Nodes]), Mod, Fun, Args)).
cast(Node, Mod, Fun, Args) ->
filter_result(?RPC:cast(rpc_node(Node), Mod, Fun, Args)).
rpc_node(Node) ->
cast(Key, Node, Mod, Fun, Args) ->
filter_result(?RPC:cast(rpc_node({Key, Node}), Mod, Fun, Args)).
rpc_node(Node) when is_atom(Node) ->
ClientNum = application:get_env(gen_rpc, tcp_client_num, ?DefaultClientNum),
{Node, rand:uniform(ClientNum)}.
{Node, rand:uniform(ClientNum)};
rpc_node({Key, Node}) when is_atom(Node) ->
ClientNum = application:get_env(gen_rpc, tcp_client_num, ?DefaultClientNum),
{Node, erlang:phash2(Key, ClientNum) + 1}.
rpc_nodes(Nodes) ->
rpc_nodes(Nodes, []).

51
src/emqx_time.erl Normal file
View File

@ -0,0 +1,51 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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_time).
-export([ seed/0
, now_secs/0
, now_secs/1
, now_ms/0
, now_ms/1
]).
-compile({inline,
[ seed/0
, now_secs/0
, now_secs/1
, now_ms/0
, now_ms/1
]}).
seed() ->
rand:seed(exsplus, erlang:timestamp()).
-spec(now_secs() -> pos_integer()).
now_secs() ->
erlang:system_time(second).
-spec(now_secs(erlang:timestamp()) -> pos_integer()).
now_secs({MegaSecs, Secs, _MicroSecs}) ->
MegaSecs * 1000000 + Secs.
-spec(now_ms() -> pos_integer()).
now_ms() ->
erlang:system_time(millisecond).
-spec(now_ms(erlang:timestamp()) -> pos_integer()).
now_ms({MegaSecs, Secs, MicroSecs}) ->
(MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000).

View File

@ -85,6 +85,8 @@
, stats/0
]).
-export_type([oom_policy/0]).
-type(ver() :: ?MQTT_PROTO_V3
| ?MQTT_PROTO_V4
| ?MQTT_PROTO_V5).
@ -186,3 +188,7 @@
-type(infos() :: #{atom() => term()}).
-type(stats() :: #{atom() => non_neg_integer()|stats()}).
-type(oom_policy() :: #{message_queue_len => non_neg_integer(),
max_heap_size => non_neg_integer()
}).

View File

@ -47,6 +47,11 @@
, get_port_info/1
]).
-ifdef(TEST).
-compile(export_all).
-compile(nowarn_export_all).
-endif.
-export([cpu_util/0]).
-define(UTIL_ALLOCATORS, [temp_alloc,

View File

@ -45,6 +45,11 @@
, terminate/3
]).
-import(emqx_misc,
[ maybe_apply/2
, start_timer/2
]).
-record(state, {
%% Peername of the ws connection.
peername :: emqx_types:peername(),
@ -52,24 +57,41 @@
sockname :: emqx_types:peername(),
%% Sock state
sockstate :: emqx_types:sockstate(),
%% Parser State
%% Simulate the active_n opt
active_n :: pos_integer(),
%% Limiter
limiter :: emqx_limiter:limiter(),
%% Limit Timer
limit_timer :: maybe(reference()),
%% Parse State
parse_state :: emqx_frame:parse_state(),
%% Serialize function
serialize :: emqx_frame:serialize_fun(),
%% Channel
channel :: emqx_channel:channel(),
%% GC State
gc_state :: maybe(emqx_gc:gc_state()),
%% Out Pending Packets
pendings :: list(emqx_types:packet()),
%% Stats Timer
stats_timer :: disabled | maybe(reference()),
%% Idle Timeout
idle_timeout :: timeout(),
%% Idle Timer
idle_timer :: reference(),
%% The stop reason
stop_reason :: term()
}).
-type(state() :: #state{}).
-define(INFO_KEYS, [socktype, peername, sockname, sockstate]).
-define(ACTIVE_N, 100).
-define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n, limiter]).
-define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt]).
-define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]).
-define(ENABLED(X), (X =/= undefined)).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
@ -92,11 +114,20 @@ info(sockname, #state{sockname = Sockname}) ->
Sockname;
info(sockstate, #state{sockstate = SockSt}) ->
SockSt;
info(active_n, #state{active_n = ActiveN}) ->
ActiveN;
info(limiter, #state{limiter = Limiter}) ->
maybe_apply(fun emqx_limiter:info/1, Limiter);
info(channel, #state{channel = Channel}) ->
emqx_channel:info(Channel);
info(stop_reason, #state{stop_reason = Reason}) ->
Reason.
attrs(State = #state{channel = Channel}) ->
ChanAttrs = emqx_channel:attrs(Channel),
SockAttrs = maps:from_list(info(?INFO_KEYS, State)),
maps:merge(ChanAttrs, #{sockinfo => SockAttrs}).
-spec(stats(pid()|state()) -> emqx_types:stats()).
stats(WsPid) when is_pid(WsPid) ->
call(WsPid, stats);
@ -128,6 +159,7 @@ call(WsPid, Req) when is_pid(WsPid) ->
%%--------------------------------------------------------------------
init(Req, Opts) ->
%% WS Transport Idle Timeout
IdleTimeout = proplists:get_value(idle_timeout, Opts, 7200000),
DeflateOptions = maps:from_list(proplists:get_value(deflate_options, Opts, [])),
MaxFrameSize = case proplists:get_value(max_frame_size, Opts, 0) of
@ -174,29 +206,41 @@ websocket_init([Req, Opts]) ->
conn_mod => ?MODULE
},
Zone = proplists:get_value(zone, Opts),
FrameOpts = emqx_zone:frame_options(Zone),
ActiveN = proplists:get_value(active_n, Opts, ?ACTIVE_N),
Limiter = emqx_limiter:init(Opts),
FrameOpts = emqx_zone:mqtt_frame_options(Zone),
ParseState = emqx_frame:initial_parse_state(FrameOpts),
Serialize = emqx_frame:serialize_fun(),
Channel = emqx_channel:init(ConnInfo, Opts),
GcState = emqx_zone:init_gc_state(Zone),
StatsTimer = emqx_zone:stats_timer(Zone),
%% MQTT Idle Timeout
IdleTimeout = emqx_zone:idle_timeout(Zone),
IdleTimer = start_timer(IdleTimeout, idle_timeout),
emqx_misc:tune_heap_size(emqx_zone:oom_policy(Zone)),
emqx_logger:set_metadata_peername(esockd_net:format(Peername)),
{ok, #state{peername = Peername,
sockname = Sockname,
sockstate = idle,
parse_state = ParseState,
serialize = Serialize,
channel = Channel,
pendings = []
}}.
{ok, #state{peername = Peername,
sockname = Sockname,
sockstate = running,
active_n = ActiveN,
limiter = Limiter,
parse_state = ParseState,
serialize = Serialize,
channel = Channel,
gc_state = GcState,
pendings = [],
stats_timer = StatsTimer,
idle_timeout = IdleTimeout,
idle_timer = IdleTimer
}, hibernate}.
websocket_handle({binary, Data}, State) when is_list(Data) ->
websocket_handle({binary, iolist_to_binary(Data)}, State);
websocket_handle({binary, Data}, State = #state{channel = Channel}) ->
websocket_handle({binary, Data}, State) ->
?LOG(debug, "RECV ~p", [Data]),
Oct = iolist_size(Data),
ok = inc_recv_stats(1, Oct),
NChannel = emqx_channel:recvd(Oct, Channel),
parse_incoming(Data, State#state{channel = NChannel});
ok = inc_recv_stats(1, iolist_size(Data)),
parse_incoming(Data, ensure_stats_timer(State));
%% Pings should be replied with pongs, cowboy does it automatically
%% Pongs can be safely ignored. Clause here simply prevents crash.
@ -215,30 +259,43 @@ websocket_info({call, From, Req}, State) ->
handle_call(From, Req, State);
websocket_info({cast, Msg}, State = #state{channel = Channel}) ->
handle_return(emqx_channel:handle_info(Msg, Channel), State);
handle_chan_return(emqx_channel:handle_info(Msg, Channel), State);
websocket_info({incoming, Packet = ?CONNECT_PACKET(ConnPkt)}, State) ->
websocket_info({incoming, Packet = ?CONNECT_PACKET(ConnPkt)},
State = #state{idle_timer = IdleTimer}) ->
ok = emqx_misc:cancel_timer(IdleTimer),
Serialize = emqx_frame:serialize_fun(ConnPkt),
NState = State#state{sockstate = running,
serialize = Serialize
NState = State#state{serialize = Serialize,
idle_timer = undefined
},
handle_incoming(Packet, NState);
websocket_info({incoming, ?PACKET(?PINGREQ)}, State) ->
reply(?PACKET(?PINGRESP), State);
websocket_info({incoming, Packet}, State) ->
handle_incoming(Packet, State);
websocket_info(Deliver = {deliver, _Topic, _Msg},
State = #state{channel = Channel}) ->
Delivers = emqx_misc:drain_deliver([Deliver]),
Result = emqx_channel:handle_out(Delivers, Channel),
handle_return(Result, State);
websocket_info(rate_limit, State) ->
InStats = #{cnt => emqx_pd:reset_counter(incoming_pubs),
oct => emqx_pd:reset_counter(incoming_bytes)
},
erlang:send(self(), {check_gc, InStats}),
ensure_rate_limit(InStats, State);
websocket_info({timeout, TRef, keepalive}, State) when is_reference(TRef) ->
RecvOct = emqx_pd:get_counter(recv_oct),
handle_timeout(TRef, {keepalive, RecvOct}, State);
websocket_info({check_gc, Stats}, State) ->
{ok, check_oom(run_gc(Stats, State))};
websocket_info({timeout, TRef, emit_stats}, State) when is_reference(TRef) ->
handle_timeout(TRef, {emit_stats, stats(State)}, State);
websocket_info({deliver, _Topic, _Msg} = Deliver, State = #state{channel = Channel}) ->
Delivers = [Deliver|emqx_misc:drain_deliver()],
Ret = emqx_channel:handle_out(Delivers, Channel),
handle_chan_return(Ret, State);
websocket_info({timeout, TRef, limit_timeout}, State = #state{limit_timer = TRef}) ->
NState = State#state{sockstate = running,
limit_timer = undefined
},
{reply, [{active, true}], NState};
websocket_info({timeout, TRef, Msg}, State) when is_reference(TRef) ->
handle_timeout(TRef, Msg, State);
@ -293,27 +350,89 @@ handle_call(From, Req, State = #state{channel = Channel}) ->
%% Handle Info
handle_info({connack, ConnAck}, State = #state{channel = Channel}) ->
ChanAttrs = emqx_channel:attrs(Channel),
SockAttrs = maps:from_list(info(?INFO_KEYS, State)),
Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}),
ok = emqx_channel:handle_info({register, Attrs, stats(State)}, Channel),
#{clientid := ClientId} = emqx_channel:info(clientinfo, Channel),
ok = emqx_cm:register_channel(ClientId),
ok = emqx_cm:set_chan_attrs(ClientId, attrs(State)),
ok = emqx_cm:set_chan_stats(ClientId, stats(State)),
reply(enqueue(ConnAck, State));
handle_info({enter, disconnected}, State = #state{channel = Channel}) ->
ChanAttrs = emqx_channel:attrs(Channel),
SockAttrs = maps:from_list(info(?INFO_KEYS, State)),
Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}),
ok = emqx_channel:handle_info({register, Attrs, stats(State)}, Channel),
#{clientid := ClientId} = emqx_channel:info(clientinfo, Channel),
emqx_cm:set_chan_attrs(ClientId, attrs(State)),
emqx_cm:set_chan_stats(ClientId, stats(State)),
reply(State);
handle_info(Info, State = #state{channel = Channel}) ->
handle_return(emqx_channel:handle_info(Info, Channel), State).
Ret = emqx_channel:handle_info(Info, Channel),
handle_chan_return(Ret, State).
%%--------------------------------------------------------------------
%% Handle timeout
handle_timeout(TRef, Msg, State = #state{channel = Channel}) ->
handle_return(emqx_channel:handle_timeout(TRef, Msg, Channel), State).
handle_timeout(TRef, idle_timeout, State = #state{idle_timer = TRef}) ->
shutdown(idle_timeout, State);
handle_timeout(TRef, keepalive, State) when is_reference(TRef) ->
RecvOct = emqx_pd:get_counter(recv_oct),
handle_timeout(TRef, {keepalive, RecvOct}, State);
handle_timeout(TRef, emit_stats, State = #state{channel = Channel,
stats_timer = TRef}) ->
#{clientid := ClientId} = emqx_channel:info(clientinfo, Channel),
(ClientId =/= undefined) andalso emqx_cm:set_chan_stats(ClientId, stats(State)),
reply(State#state{stats_timer = undefined});
handle_timeout(TRef, TMsg, State = #state{channel = Channel}) ->
Ret = emqx_channel:handle_timeout(TRef, TMsg, Channel),
handle_chan_return(Ret, State).
%%--------------------------------------------------------------------
%% Ensure stats timer
-compile({inline, [ensure_stats_timer/1]}).
ensure_stats_timer(State = #state{idle_timeout = Timeout,
stats_timer = undefined}) ->
State#state{stats_timer = start_timer(Timeout, emit_stats)};
ensure_stats_timer(State) -> State.
%%--------------------------------------------------------------------
%% Ensure rate limit
ensure_rate_limit(Stats, State = #state{limiter = Limiter}) ->
case ?ENABLED(Limiter) andalso emqx_limiter:check(Stats, Limiter) of
false -> {ok, State};
{ok, Limiter1} ->
{ok, State#state{limiter = Limiter1}};
{pause, Time, Limiter1} ->
?LOG(debug, "Pause ~pms due to rate limit", [Time]),
TRef = start_timer(Time, limit_timeout),
NState = State#state{sockstate = blocked,
limiter = Limiter1,
limit_timer = TRef
},
{reply, [{active, false}], NState}
end.
%%--------------------------------------------------------------------
%% Run GC and Check OOM
run_gc(Stats, State = #state{gc_state = GcSt}) ->
case ?ENABLED(GcSt) andalso emqx_gc:run(Stats, GcSt) of
false -> State;
{IsGC, GcSt1} ->
IsGC andalso emqx_metrics:inc('channel.gc.cnt'),
State#state{gc_state = GcSt1}
end.
check_oom(State = #state{channel = Channel}) ->
#{zone := Zone} = emqx_channel:info(clientinfo, Channel),
OomPolicy = emqx_zone:oom_policy(Zone),
case ?ENABLED(OomPolicy) andalso emqx_misc:check_oom(OomPolicy) of
Shutdown = {shutdown, _Reason} ->
erlang:send(self(), Shutdown);
_Other -> ok
end,
State.
%%--------------------------------------------------------------------
%% Parse incoming data
@ -326,7 +445,7 @@ parse_incoming(Data, State = #state{parse_state = ParseState}) ->
{more, NParseState} ->
{ok, State#state{parse_state = NParseState}};
{ok, Packet, Rest, NParseState} ->
self() ! {incoming, Packet},
erlang:send(self(), {incoming, Packet}),
parse_incoming(Rest, State#state{parse_state = NParseState})
catch
error:Reason:Stk ->
@ -337,52 +456,60 @@ parse_incoming(Data, State = #state{parse_state = ParseState}) ->
end.
%%--------------------------------------------------------------------
%% Handle incoming packets
%% Handle incoming packet
handle_incoming(Packet = ?PACKET(Type), State = #state{channel = Channel}) ->
_ = inc_incoming_stats(Type),
_ = emqx_metrics:inc_recv(Packet),
handle_incoming(Packet, State = #state{active_n = ActiveN, channel = Channel})
when is_record(Packet, mqtt_packet) ->
?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]),
handle_return(emqx_channel:handle_in(Packet, Channel), State);
ok = inc_incoming_stats(Packet),
(emqx_pd:get_counter(incoming_pubs) > ActiveN)
andalso erlang:send(self(), rate_limit),
Ret = emqx_channel:handle_in(Packet, Channel),
handle_chan_return(Ret, State);
handle_incoming(FrameError, State = #state{channel = Channel}) ->
handle_return(emqx_channel:handle_in(FrameError, Channel), State).
handle_chan_return(emqx_channel:handle_in(FrameError, Channel), State).
%%--------------------------------------------------------------------
%% Handle channel return
handle_return(ok, State) ->
handle_chan_return(ok, State) ->
reply(State);
handle_return({ok, NChannel}, State) ->
handle_chan_return({ok, NChannel}, State) ->
reply(State#state{channel= NChannel});
handle_return({ok, Replies, NChannel}, State) ->
handle_chan_return({ok, Replies, NChannel}, State) ->
reply(Replies, State#state{channel= NChannel});
handle_return({shutdown, Reason, NChannel}, State) ->
handle_chan_return({shutdown, Reason, NChannel}, State) ->
stop(Reason, State#state{channel = NChannel});
handle_return({shutdown, Reason, OutPacket, NChannel}, State) ->
handle_chan_return({shutdown, Reason, OutPacket, NChannel}, State) ->
NState = State#state{channel = NChannel},
stop(Reason, enqueue(OutPacket, NState)).
%%--------------------------------------------------------------------
%% Handle outgoing packets
handle_outgoing(Packets, State = #state{channel = Channel}) ->
handle_outgoing(Packets, State = #state{active_n = ActiveN}) ->
IoData = lists:map(serialize_and_inc_stats_fun(State), Packets),
Oct = iolist_size(IoData),
ok = inc_sent_stats(length(Packets), Oct),
NChannel = emqx_channel:sent(Oct, Channel),
{{binary, IoData}, State#state{channel = NChannel}}.
case emqx_pd:get_counter(outgoing_pubs) > ActiveN of
true ->
OutStats = #{cnt => emqx_pd:reset_counter(outgoing_pubs),
oct => emqx_pd:reset_counter(outgoing_bytes)
},
erlang:send(self(), {check_gc, OutStats});
false -> ok
end,
{{binary, IoData}, ensure_stats_timer(State)}.
%% TODO: Duplicated with emqx_channel:serialize_and_inc_stats_fun/1
serialize_and_inc_stats_fun(#state{serialize = Serialize}) ->
fun(Packet = ?PACKET(Type)) ->
fun(Packet) ->
case Serialize(Packet) of
<<>> -> ?LOG(warning, "~s is discarded due to the frame is too large!",
[emqx_packet:format(Packet)]),
<<>>;
Data -> _ = inc_outgoing_stats(Type),
_ = emqx_metrics:inc_sent(Packet),
?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]),
Data -> ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]),
ok = inc_outgoing_stats(Packet),
Data
end
end.
@ -398,23 +525,33 @@ serialize_and_inc_stats_fun(#state{serialize = Serialize}) ->
]}).
inc_recv_stats(Cnt, Oct) ->
emqx_pd:update_counter(recv_cnt, Cnt),
emqx_pd:update_counter(recv_oct, Oct),
emqx_pd:inc_counter(incoming_bytes, Oct),
emqx_pd:inc_counter(recv_cnt, Cnt),
emqx_pd:inc_counter(recv_oct, Oct),
emqx_metrics:inc('bytes.received', Oct).
inc_incoming_stats(Type) ->
emqx_pd:update_counter(recv_pkt, 1),
(Type == ?PUBLISH)
andalso emqx_pd:update_counter(recv_msg, 1).
inc_incoming_stats(Packet = ?PACKET(Type)) ->
emqx_pd:inc_counter(recv_pkt, 1),
if Type == ?PUBLISH ->
emqx_pd:inc_counter(recv_msg, 1),
emqx_pd:inc_counter(incoming_pubs, 1);
true -> ok
end,
emqx_metrics:inc_recv(Packet).
inc_outgoing_stats(Type) ->
emqx_pd:update_counter(send_pkt, 1),
(Type == ?PUBLISH)
andalso emqx_pd:update_counter(send_msg, 1).
inc_outgoing_stats(Packet = ?PACKET(Type)) ->
emqx_pd:inc_counter(send_pkt, 1),
if Type == ?PUBLISH ->
emqx_pd:inc_counter(send_msg, 1),
emqx_pd:inc_counter(outgoing_pubs, 1);
true -> ok
end,
emqx_metrics:inc_sent(Packet).
inc_sent_stats(Cnt, Oct) ->
emqx_pd:update_counter(send_cnt, Cnt),
emqx_pd:update_counter(send_oct, Oct),
emqx_pd:inc_counter(outgoing_bytes, Oct),
emqx_pd:inc_counter(send_cnt, Cnt),
emqx_pd:inc_counter(send_oct, Oct),
emqx_metrics:inc('bytes.sent', Oct).
%%--------------------------------------------------------------------
@ -451,6 +588,9 @@ enqueue(Packet, State) when is_record(Packet, mqtt_packet) ->
enqueue(Packets, State = #state{pendings = Pendings}) ->
State#state{pendings = lists:append(Pendings, Packets)}.
shutdown(Reason, State) ->
stop({shutdown, Reason}, State).
stop(Reason, State = #state{pendings = []}) ->
{stop, State#state{stop_reason = Reason}};
stop(Reason, State = #state{pendings = Pendings}) ->

View File

@ -25,31 +25,63 @@
-logger_header("[Zone]").
-compile({inline,
[ idle_timeout/1
, publish_limit/1
, mqtt_frame_options/1
, mqtt_strict_mode/1
, max_packet_size/1
, mountpoint/1
, use_username_as_clientid/1
, stats_timer/1
, enable_stats/1
, enable_acl/1
, enable_ban/1
, enable_flapping_detect/1
, ignore_loop_deliver/1
, server_keepalive/1
, keepalive_backoff/1
, max_inflight/1
, session_expiry_interval/1
, force_gc_policy/1
, force_shutdown_policy/1
]}).
%% APIs
-export([start_link/0, stop/0]).
-export([ frame_options/1
%% Zone Option API
-export([ idle_timeout/1
, publish_limit/1
, mqtt_frame_options/1
, mqtt_strict_mode/1
, max_packet_size/1
, mountpoint/1
, use_username_as_clientid/1
, stats_timer/1
, enable_stats/1
, enable_acl/1
, enable_ban/1
, enable_flapping_detect/1
, ignore_loop_deliver/1
, server_keepalive/1
, keepalive_backoff/1
, max_inflight/1
, session_expiry_interval/1
, force_gc_policy/1
, force_shutdown_policy/1
]).
-export([check_oom/2]).
-export([ init_gc_state/1
, oom_policy/1
]).
%% Zone API
-export([ get_env/2
, get_env/3
, set_env/3
, unset_env/2
, unset_all_env/0
]).
-export([force_reload/0]).
@ -63,27 +95,46 @@
, code_change/3
]).
-export_type([zone/0]).
-import(emqx_misc, [maybe_apply/2]).
%% dummy state
-record(state, {}).
-export_type([zone/0]).
-type(zone() :: atom()).
-define(TAB, ?MODULE).
-define(SERVER, ?MODULE).
-define(DEFAULT_IDLE_TIMEOUT, 30000).
-define(KEY(Zone, Key), {?MODULE, Zone, Key}).
%%--------------------------------------------------------------------
%% APIs
%%--------------------------------------------------------------------
-spec(start_link() -> startlink_ret()).
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
-spec(frame_options(zone()) -> emqx_frame:options()).
frame_options(Zone) ->
-spec(stop() -> ok).
stop() ->
gen_server:stop(?SERVER).
-spec(init_gc_state(zone()) -> emqx_gc:gc_state()).
init_gc_state(Zone) ->
maybe_apply(fun emqx_gc:init/1, force_gc_policy(Zone)).
-spec(oom_policy(zone()) -> emqx_types:oom_policy()).
oom_policy(Zone) -> force_shutdown_policy(Zone).
%%--------------------------------------------------------------------
%% Zone Options API
%%--------------------------------------------------------------------
-spec(idle_timeout(zone()) -> pos_integer()).
idle_timeout(Zone) ->
get_env(Zone, idle_timeout, ?DEFAULT_IDLE_TIMEOUT).
-spec(publish_limit(zone()) -> maybe(esockd_rate_limit:config())).
publish_limit(Zone) ->
get_env(Zone, publish_limit).
-spec(mqtt_frame_options(zone()) -> emqx_frame:options()).
mqtt_frame_options(Zone) ->
#{strict_mode => mqtt_strict_mode(Zone),
max_size => max_packet_size(Zone)
}.
@ -96,10 +147,17 @@ mqtt_strict_mode(Zone) ->
max_packet_size(Zone) ->
get_env(Zone, max_packet_size, ?MAX_PACKET_SIZE).
-spec(mountpoint(zone()) -> maybe(emqx_mountpoint:mountpoint())).
mountpoint(Zone) -> get_env(Zone, mountpoint).
-spec(use_username_as_clientid(zone()) -> boolean()).
use_username_as_clientid(Zone) ->
get_env(Zone, use_username_as_clientid, false).
-spec(stats_timer(zone()) -> undefined | disabled).
stats_timer(Zone) ->
case enable_stats(Zone) of true -> undefined; false -> disabled end.
-spec(enable_stats(zone()) -> boolean()).
enable_stats(Zone) ->
get_env(Zone, enable_stats, true).
@ -124,6 +182,10 @@ ignore_loop_deliver(Zone) ->
server_keepalive(Zone) ->
get_env(Zone, server_keepalive).
-spec(keepalive_backoff(zone()) -> float()).
keepalive_backoff(Zone) ->
get_env(Zone, keepalive_backoff, 0.75).
-spec(max_inflight(zone()) -> 0..65535).
max_inflight(Zone) ->
get_env(Zone, max_inflight, 65535).
@ -140,18 +202,9 @@ force_gc_policy(Zone) ->
force_shutdown_policy(Zone) ->
get_env(Zone, force_shutdown_policy).
-spec(check_oom(zone(), fun()) -> ok | term()).
check_oom(Zone, Action) ->
case emqx_zone:force_shutdown_policy(Zone) of
undefined -> ok;
Policy -> do_check_oom(emqx_oom:init(Policy), Action)
end.
do_check_oom(OomPolicy, Action) ->
case emqx_oom:check(OomPolicy) of
ok -> ok;
Shutdown -> Action(Shutdown)
end.
%%--------------------------------------------------------------------
%% APIs
%%--------------------------------------------------------------------
-spec(get_env(maybe(zone()), atom()) -> maybe(term())).
get_env(undefined, Key) -> emqx:get_env(Key);
@ -175,21 +228,22 @@ set_env(Zone, Key, Val) ->
unset_env(Zone, Key) ->
persistent_term:erase(?KEY(Zone, Key)).
-spec(unset_all_env() -> ok).
unset_all_env() ->
[unset_env(Zone, Key) || {?KEY(Zone, Key), _Val} <- persistent_term:get()],
ok.
-spec(force_reload() -> ok).
force_reload() ->
gen_server:call(?SERVER, force_reload).
-spec(stop() -> ok).
stop() ->
gen_server:stop(?SERVER, normal, infinity).
%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------
init([]) ->
_ = do_reload(),
{ok, #state{}}.
{ok, #{}}.
handle_call(force_reload, _From, State) ->
_ = do_reload(),

View File

@ -1,120 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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_zone_options).
-compile(inline).
-include("types.hrl").
-include("emqx_mqtt.hrl").
-export([ idle_timeout/1
, publish_limit/1
, mqtt_frame_options/1
, mqtt_strict_mode/1
, max_packet_size/1
, mountpoint/1
, use_username_as_clientid/1
, enable_stats/1
, enable_acl/1
, enable_ban/1
, enable_flapping_detect/1
, ignore_loop_deliver/1
, server_keepalive/1
, keepalive_backoff/1
, max_inflight/1
, session_expiry_interval/1
, force_gc_policy/1
, force_shutdown_policy/1
]).
-import(emqx_zone, [get_env/2, get_env/3]).
-define(DEFAULT_IDLE_TIMEOUT, 30000).
-spec(idle_timeout(emqx_zone:zone()) -> pos_integer()).
idle_timeout(Zone) ->
get_env(Zone, idle_timeout, ?DEFAULT_IDLE_TIMEOUT).
-spec(publish_limit(emqx_zone:zone()) -> maybe(esockd_rate_limit:bucket())).
publish_limit(Zone) ->
get_env(Zone, publish_limit).
-spec(mqtt_frame_options(emqx_zone:zone()) -> emqx_frame:options()).
mqtt_frame_options(Zone) ->
#{strict_mode => mqtt_strict_mode(Zone),
max_size => max_packet_size(Zone)
}.
-spec(mqtt_strict_mode(emqx_zone:zone()) -> boolean()).
mqtt_strict_mode(Zone) ->
get_env(Zone, mqtt_strict_mode, false).
-spec(max_packet_size(emqx_zone:zone()) -> integer()).
max_packet_size(Zone) ->
get_env(Zone, max_packet_size, ?MAX_PACKET_SIZE).
-spec(mountpoint(emqx_zone:zone()) -> maybe(emqx_mountpoint:mountpoint())).
mountpoint(Zone) -> get_env(Zone, mountpoint).
-spec(use_username_as_clientid(emqx_zone:zone()) -> boolean()).
use_username_as_clientid(Zone) ->
get_env(Zone, use_username_as_clientid, false).
-spec(enable_stats(emqx_zone:zone()) -> boolean()).
enable_stats(Zone) ->
get_env(Zone, enable_stats, true).
-spec(enable_acl(emqx_zone:zone()) -> boolean()).
enable_acl(Zone) ->
get_env(Zone, enable_acl, true).
-spec(enable_ban(emqx_zone:zone()) -> boolean()).
enable_ban(Zone) ->
get_env(Zone, enable_ban, false).
-spec(enable_flapping_detect(emqx_zone:zone()) -> boolean()).
enable_flapping_detect(Zone) ->
get_env(Zone, enable_flapping_detect, false).
-spec(ignore_loop_deliver(emqx_zone:zone()) -> boolean()).
ignore_loop_deliver(Zone) ->
get_env(Zone, ignore_loop_deliver, false).
-spec(server_keepalive(emqx_zone:zone()) -> pos_integer()).
server_keepalive(Zone) ->
get_env(Zone, server_keepalive).
-spec(keepalive_backoff(emqx_zone:zone()) -> float()).
keepalive_backoff(Zone) ->
get_env(Zone, keepalive_backoff, 0.75).
-spec(max_inflight(emqx_zone:zone()) -> 0..65535).
max_inflight(Zone) ->
get_env(Zone, max_inflight, 65535).
-spec(session_expiry_interval(emqx_zone:zone()) -> non_neg_integer()).
session_expiry_interval(Zone) ->
get_env(Zone, session_expiry_interval, 0).
-spec(force_gc_policy(emqx_zone:zone()) -> maybe(emqx_gc:opts())).
force_gc_policy(Zone) ->
get_env(Zone, force_gc_policy).
-spec(force_shutdown_policy(emqx_zone:zone()) -> maybe(emqx_oom:opts())).
force_shutdown_policy(Zone) ->
get_env(Zone, force_shutdown_policy).

View File

@ -25,7 +25,6 @@
all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
Config.
@ -33,58 +32,17 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
t_start(_) ->
error('TODO').
t_restart(_) ->
error('TODO').
t_stop(_) ->
error('TODO').
t_is_running(_) ->
error('TODO').
t_subscribe(_) ->
error('TODO').
t_publish(_) ->
error('TODO').
t_unsubscribe(_) ->
error('TODO').
t_topics(_) ->
error('TODO').
t_subscribers(_) ->
error('TODO').
t_subscriptions(_) ->
error('TODO').
t_subscribed(_) ->
error('TODO').
t_hook(_) ->
error('TODO').
t_unhook(_) ->
error('TODO').
t_run_hook(_) ->
error('TODO').
t_run_fold_hook(_) ->
error('TODO').
t_shutdown(_) ->
error('TODO').
t_reboot(_) ->
error('TODO').
t_stop_start(_) ->
emqx:stop(),
false = emqx:is_running(node()),
emqx:start(),
true = emqx:is_running(node()),
ok = emqx:shutdown(),
false = emqx:is_running(node()),
ok = emqx:reboot(),
true = emqx:is_running(node()),
ok = emqx:shutdown(for_test),
false = emqx:is_running(node()).
t_get_env(_) ->
?assertEqual(undefined, emqx:get_env(undefined_key)),
@ -100,18 +58,28 @@ t_emqx_pubsub_api(_) ->
{ok, C} = emqtt:start_link([{host, "localhost"}, {clientid, "myclient"}]),
{ok, _} = emqtt:connect(C),
ClientId = <<"myclient">>,
Topic = <<"mytopic">>,
Payload = <<"Hello World">>,
Topic = <<"mytopic">>,
Topic1 = <<"mytopic1">>,
Topic2 = <<"mytopic2">>,
Topic3 = <<"mytopic3">>,
emqx:subscribe(Topic, ClientId),
emqx:subscribe(Topic1, ClientId, #{qos => 1}),
emqx:subscribe(Topic2, ClientId, #{qos => 2}),
ct:sleep(100),
?assertEqual([Topic], emqx:topics()),
?assertEqual([Topic2, Topic1, Topic], emqx:topics()),
?assertEqual([self()], emqx:subscribers(Topic)),
?assertEqual([{Topic,#{qos => 0,subid => ClientId}}], emqx:subscriptions(self())),
?assertEqual([self()], emqx:subscribers(Topic1)),
?assertEqual([self()], emqx:subscribers(Topic2)),
?assertEqual([{Topic,#{qos => 0,subid => ClientId}}, {Topic1,#{qos => 1,subid => ClientId}}, {Topic2,#{qos => 2,subid => ClientId}}], emqx:subscriptions(self())),
?assertEqual(true, emqx:subscribed(self(), Topic)),
?assertEqual(true, emqx:subscribed(ClientId, Topic)),
?assertEqual(false, emqx:subscribed(self(), Topic1)),
?assertEqual(false, emqx:subscribed(ClientId, Topic1)),
?assertEqual(true, emqx:subscribed(self(), Topic1)),
?assertEqual(true, emqx:subscribed(ClientId, Topic1)),
?assertEqual(true, emqx:subscribed(self(), Topic2)),
?assertEqual(true, emqx:subscribed(ClientId, Topic2)),
?assertEqual(false, emqx:subscribed(self(), Topic3)),
?assertEqual(false, emqx:subscribed(ClientId, Topic3)),
emqx:publish(emqx_message:make(Topic, Payload)),
receive
{deliver, Topic, #message{payload = Payload}} ->
@ -119,26 +87,100 @@ t_emqx_pubsub_api(_) ->
after 100 ->
ct:fail("no_message")
end,
emqx:publish(emqx_message:make(Topic1, Payload)),
receive
{deliver, Topic1, #message{payload = Payload}} ->
ok
after 100 ->
ct:fail("no_message")
end,
emqx:publish(emqx_message:make(Topic2, Payload)),
receive
{deliver, Topic2, #message{payload = Payload}} ->
ok
after 100 ->
ct:fail("no_message")
end,
emqx:unsubscribe(Topic),
emqx:unsubscribe(Topic1),
emqx:unsubscribe(Topic2),
ct:sleep(20),
?assertEqual([], emqx:topics()).
t_emqx_hook_api(_) ->
InitArgs = ['arg2', 'arg3'],
emqx:hook('hook.run', fun run/3, InitArgs),
ok = emqx:run_hook('hook.run', ['arg1']),
emqx:unhook('hook.run', fun run/3),
t_hook_unhook(_) ->
ok = emqx:hook(test_hook, fun ?MODULE:hook_fun1/1, []),
ok = emqx:hook(test_hook, fun ?MODULE:hook_fun2/1, []),
?assertEqual({error, already_exists},
emqx:hook(test_hook, fun ?MODULE:hook_fun2/1, [])),
ok = emqx:unhook(test_hook, fun ?MODULE:hook_fun1/1),
ok = emqx:unhook(test_hook, fun ?MODULE:hook_fun2/1),
emqx:hook('hook.run_fold', fun add1/1),
emqx:hook('hook.run_fold', fun add2/1),
4 = emqx:run_fold_hook('hook.run_fold', [], 1),
emqx:unhook('hook.run_fold', fun add1/1),
emqx:unhook('hook.run_fold', fun add2/1).
ok = emqx:hook(emqx_hook, {?MODULE, hook_fun8, []}, 8),
ok = emqx:hook(emqx_hook, {?MODULE, hook_fun2, []}, 2),
ok = emqx:hook(emqx_hook, {?MODULE, hook_fun10, []}, 10),
ok = emqx:hook(emqx_hook, {?MODULE, hook_fun9, []}, 9),
ok = emqx:unhook(emqx_hook, {?MODULE, hook_fun2, []}),
ok = emqx:unhook(emqx_hook, {?MODULE, hook_fun8, []}),
ok = emqx:unhook(emqx_hook, {?MODULE, hook_fun9, []}),
ok = emqx:unhook(emqx_hook, {?MODULE, hook_fun10, []}).
run('arg1', 'arg2', 'arg3') ->
ok;
run(_, _, _) ->
ct:fail("no_match").
t_run_hook(_) ->
ok = emqx:hook(foldl_hook, fun ?MODULE:hook_fun3/4, [init]),
ok = emqx:hook(foldl_hook, {?MODULE, hook_fun3, [init]}),
ok = emqx:hook(foldl_hook, fun ?MODULE:hook_fun4/4, [init]),
ok = emqx:hook(foldl_hook, fun ?MODULE:hook_fun5/4, [init]),
[r5,r4] = emqx:run_fold_hook(foldl_hook, [arg1, arg2], []),
[] = emqx:run_fold_hook(unknown_hook, [], []),
add1(N) -> {ok, N + 1}.
add2(N) -> {ok, N + 2}.
ok = emqx:hook(foldl_hook2, fun ?MODULE:hook_fun9/2),
ok = emqx:hook(foldl_hook2, {?MODULE, hook_fun10, []}),
[r9] = emqx:run_fold_hook(foldl_hook2, [arg], []),
ok = emqx:hook(foreach_hook, fun ?MODULE:hook_fun6/2, [initArg]),
{error, already_exists} = emqx:hook(foreach_hook, fun ?MODULE:hook_fun6/2, [initArg]),
ok = emqx:hook(foreach_hook, fun ?MODULE:hook_fun7/2, [initArg]),
ok = emqx:hook(foreach_hook, fun ?MODULE:hook_fun8/2, [initArg]),
ok = emqx:run_hook(foreach_hook, [arg]),
ok = emqx:hook(foreach_filter1_hook, {?MODULE, hook_fun1, []}, {?MODULE, hook_filter1, []}, 0),
?assertEqual(ok, emqx:run_hook(foreach_filter1_hook, [arg])), %% filter passed
?assertEqual(ok, emqx:run_hook(foreach_filter1_hook, [arg1])), %% filter failed
ok = emqx:hook(foldl_filter2_hook, {?MODULE, hook_fun2, []}, {?MODULE, hook_filter2, [init_arg]}),
ok = emqx:hook(foldl_filter2_hook, {?MODULE, hook_fun2_1, []}, {?MODULE, hook_filter2_1, [init_arg]}),
?assertEqual(3, emqx:run_fold_hook(foldl_filter2_hook, [arg], 1)),
?assertEqual(2, emqx:run_fold_hook(foldl_filter2_hook, [arg1], 1)).
%%--------------------------------------------------------------------
%% Hook fun
%%--------------------------------------------------------------------
hook_fun1(arg) -> ok;
hook_fun1(_) -> error.
hook_fun2(arg) -> ok;
hook_fun2(_) -> error.
hook_fun2(_, Acc) -> {ok, Acc + 1}.
hook_fun2_1(_, Acc) -> {ok, Acc + 1}.
hook_fun3(arg1, arg2, _Acc, init) -> ok.
hook_fun4(arg1, arg2, Acc, init) -> {ok, [r4 | Acc]}.
hook_fun5(arg1, arg2, Acc, init) -> {ok, [r5 | Acc]}.
hook_fun6(arg, initArg) -> ok.
hook_fun7(arg, initArg) -> ok.
hook_fun8(arg, initArg) -> ok.
hook_fun9(arg, Acc) -> {stop, [r9 | Acc]}.
hook_fun10(arg, Acc) -> {stop, [r10 | Acc]}.
hook_filter1(arg) -> true;
hook_filter1(_) -> false.
hook_filter2(arg, _Acc, init_arg) -> true;
hook_filter2(_, _Acc, _IntArg) -> false.
hook_filter2_1(arg, _Acc, init_arg) -> true;
hook_filter2_1(arg1, _Acc, init_arg) -> true;
hook_filter2_1(_, _Acc, _IntArg) -> false.

View File

@ -29,12 +29,12 @@ init_per_testcase(_TestCase, Config) ->
end_per_testcase(_TestCase, Config) ->
Config.
t_authenticate(_) ->
error('TODO').
% t_authenticate(_) ->
% error('TODO').
t_check_acl(_) ->
error('TODO').
% t_check_acl(_) ->
% error('TODO').
t_reload_acl(_) ->
error('TODO').
% t_reload_acl(_) ->
% error('TODO').

View File

@ -29,9 +29,9 @@ init_per_testcase(_TestCase, Config) ->
end_per_testcase(_TestCase, Config) ->
Config.
t_compile(_) ->
error('TODO').
% t_compile(_) ->
% error('TODO').
t_match(_) ->
error('TODO').
% t_match(_) ->
% error('TODO').

View File

@ -23,45 +23,73 @@
all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) ->
emqx_ct_helpers:boot_modules(all),
emqx_ct_helpers:start_apps([]),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
init_per_testcase(_TestCase, Config) ->
Config.
end_per_testcase(_TestCase, Config) ->
Config.
t_cache_k(_) ->
error('TODO').
t_clean_acl_cache(_Config) ->
{ok, Client} = emqtt:start_link([{clientid, <<"emqx_c">>}]),
{ok, _} = emqtt:connect(Client),
{ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0),
emqtt:publish(Client, <<"t1">>, <<"{\"x\":1}">>, 0),
ct:sleep(100),
ClientPid = case emqx_cm:lookup_channels(<<"emqx_c">>) of
[Pid] when is_pid(Pid) ->
Pid;
Pids when is_list(Pids) ->
lists:last(Pids);
_ -> {error, not_found}
end,
Caches = gen_server:call(ClientPid, list_acl_cache),
ct:log("acl caches: ~p", [Caches]),
?assert(length(Caches) > 0),
erlang:send(ClientPid, clean_acl_cache),
?assertEqual(0, length(gen_server:call(ClientPid, list_acl_cache))),
emqtt:stop(Client).
t_cache_v(_) ->
error('TODO').
% t_cache_k(_) ->
% error('TODO').
t_cleanup_acl_cache(_) ->
error('TODO').
% t_cache_v(_) ->
% error('TODO').
t_get_oldest_key(_) ->
error('TODO').
% t_cleanup_acl_cache(_) ->
% error('TODO').
t_get_newest_key(_) ->
error('TODO').
% t_get_oldest_key(_) ->
% error('TODO').
t_get_cache_max_size(_) ->
error('TODO').
% t_get_newest_key(_) ->
% error('TODO').
t_get_cache_size(_) ->
error('TODO').
% t_get_cache_max_size(_) ->
% error('TODO').
t_dump_acl_cache(_) ->
error('TODO').
% t_get_cache_size(_) ->
% error('TODO').
t_empty_acl_cache(_) ->
error('TODO').
% t_dump_acl_cache(_) ->
% error('TODO').
t_put_acl_cache(_) ->
error('TODO').
% t_empty_acl_cache(_) ->
% error('TODO').
t_get_acl_cache(_) ->
error('TODO').
% t_put_acl_cache(_) ->
% error('TODO').
t_is_enabled(_) ->
error('TODO').
% t_get_acl_cache(_) ->
% error('TODO').
% t_is_enabled(_) ->
% error('TODO').

View File

@ -82,8 +82,18 @@ t_alarm_handler(_) ->
{ok, ?PUBLISH_PACKET(?QOS_0, Topic2, _, _), <<>>, _} = raw_recv_parse(Data4),
?assertEqual(false, lists:keymember(alarm_for_test, 1, emqx_alarm_handler:get_alarms()))
?assertEqual(false, lists:keymember(alarm_for_test, 1, emqx_alarm_handler:get_alarms())),
emqx_alarm_handler:mnesia(copy),
?assertEqual(true, lists:keymember(alarm_for_test, 1, emqx_alarm_handler:get_alarms(history))),
alarm_handler:clear_alarm(not_exist),
gen_event:start({local, alarm_handler_2}, []),
gen_event:add_handler(alarm_handler_2, emqx_alarm_handler, []),
?assertEqual({error,bad_query}, gen_event:call(alarm_handler_2, emqx_alarm_handler, bad_query)),
?assertEqual(ok, gen_event:notify(alarm_handler_2, ignored)),
gen_event:stop(alarm_handler_2)
end).
with_connection(DoFun) ->

View File

@ -24,13 +24,6 @@
all() -> emqx_ct:all(?MODULE).
t_encode(_) ->
error('TODO').
t_decode(_) ->
error('TODO').
t_proper_base62(_) ->
Opts = [{numtests, 100}, {to_file, user}],
?assert(proper:quickcheck(prop_symmetric(), Opts)),

View File

@ -23,22 +23,6 @@
all() -> emqx_ct:all(?MODULE).
t_init(_) ->
error('TODO').
t_push(_) ->
error('TODO').
t_commit(_) ->
error('TODO').
t_size(_) ->
error('TODO').
t_items(_) ->
error('TODO').
t_batch_full_commit(_) ->
B0 = emqx_batch:init(#{batch_size => 3,
linger_ms => 2000,

View File

@ -29,24 +29,8 @@
all() -> emqx_ct:all(?MODULE).
groups() ->
[{pubsub, [sequence],
[t_sub_unsub,
t_publish,
t_pubsub,
t_shared_subscribe,
t_dispatch_with_no_sub,
't_pubsub#',
't_pubsub+'
]},
{metrics, [sequence],
[inc_dec_metric]},
{stats, [sequence],
[set_get_stat]
}].
init_per_suite(Config) ->
emqx_ct_helpers:boot_modules([router, broker]),
emqx_ct_helpers:boot_modules(all),
emqx_ct_helpers:start_apps([]),
Config.
@ -57,156 +41,173 @@ end_per_suite(_Config) ->
%% PubSub Test
%%--------------------------------------------------------------------
t_sub_unsub(_) ->
ok = emqx_broker:subscribe(<<"topic">>, <<"clientId">>),
ok = emqx_broker:subscribe(<<"topic/1">>, <<"clientId">>, #{qos => 1}),
ok = emqx_broker:subscribe(<<"topic/2">>, <<"clientId">>, #{qos => 2}),
true = emqx_broker:subscribed(<<"clientId">>, <<"topic">>),
Topics = emqx_broker:topics(),
lists:foreach(fun(Topic) ->
?assert(lists:member(Topic, Topics))
end, Topics),
ok = emqx_broker:unsubscribe(<<"topic">>),
ok = emqx_broker:unsubscribe(<<"topic/1">>),
ok = emqx_broker:unsubscribe(<<"topic/2">>).
t_subscribed(_) ->
emqx_broker:subscribe(<<"topic">>),
?assertEqual(false, emqx_broker:subscribed(undefined, <<"topic">>)),
?assertEqual(true, emqx_broker:subscribed(self(), <<"topic">>)),
emqx_broker:unsubscribe(<<"topic">>).
t_publish(_) ->
Msg = emqx_message:make(ct, <<"test/pubsub">>, <<"hello">>),
ok = emqx_broker:subscribe(<<"test/+">>),
timer:sleep(10),
emqx_broker:publish(Msg),
?assert(receive {deliver, <<"test/+">>, #message{payload = <<"hello">>}} -> true after 100 -> false end).
t_subscribed_2(_) ->
emqx_broker:subscribe(<<"topic">>, <<"clientid">>),
?assertEqual(true, emqx_broker:subscribed(<<"clientid">>, <<"topic">>)),
?assertEqual(true, emqx_broker:subscribed(self(), <<"topic">>)),
emqx_broker:unsubscribe(<<"topic">>).
t_dispatch_with_no_sub(_) ->
Msg = emqx_message:make(ct, <<"no_subscribers">>, <<"hello">>),
Delivery = #delivery{sender = self(), message = Msg},
?assertEqual([{node(),<<"no_subscribers">>,{error,no_subscribers}}],
emqx_broker:route([{<<"no_subscribers">>, node()}], Delivery)).
t_subopts(_) ->
?assertEqual(false, emqx_broker:set_subopts(<<"topic">>, #{qos => 1})),
?assertEqual(undefined, emqx_broker:get_subopts(self(), <<"topic">>)),
?assertEqual(undefined, emqx_broker:get_subopts(<<"clientid">>, <<"topic">>)),
emqx_broker:subscribe(<<"topic">>, <<"clientid">>, #{qos => 1}),
?assertEqual(#{qos => 1, subid => <<"clientid">>}, emqx_broker:get_subopts(self(), <<"topic">>)),
?assertEqual(#{qos => 1, subid => <<"clientid">>}, emqx_broker:get_subopts(<<"clientid">>,<<"topic">>)),
emqx_broker:subscribe(<<"topic">>, <<"clientid">>, #{qos => 2}),
?assertEqual(#{qos => 1, subid => <<"clientid">>}, emqx_broker:get_subopts(self(), <<"topic">>)),
?assertEqual(true, emqx_broker:set_subopts(<<"topic">>, #{qos => 2})),
?assertEqual(#{qos => 2, subid => <<"clientid">>}, emqx_broker:get_subopts(self(), <<"topic">>)),
emqx_broker:unsubscribe(<<"topic">>).
t_pubsub(_) ->
true = emqx:is_running(node()),
Self = self(),
Subscriber = <<"clientId">>,
ok = emqx_broker:subscribe(<<"a/b/c">>, Subscriber, #{ qos => 1 }),
#{qos := 1} = ets:lookup_element(emqx_suboption, {Self, <<"a/b/c">>}, 2),
#{qos := 1} = emqx_broker:get_subopts(Subscriber, <<"a/b/c">>),
true = emqx_broker:set_subopts(<<"a/b/c">>, #{qos => 0}),
#{qos := 0} = emqx_broker:get_subopts(Subscriber, <<"a/b/c">>),
ok = emqx_broker:subscribe(<<"a/b/c">>, Subscriber, #{ qos => 2 }),
%% ct:log("Emq Sub: ~p.~n", [ets:lookup(emqx_suboption, {<<"a/b/c">>, Subscriber})]),
timer:sleep(10),
[Self] = emqx_broker:subscribers(<<"a/b/c">>),
emqx_broker:publish(
emqx_message:make(ct, <<"a/b/c">>, <<"hello">>)),
t_topics(_) ->
Topics = [<<"topic">>, <<"topic/1">>, <<"topic/2">>],
ok = emqx_broker:subscribe(lists:nth(1, Topics), <<"clientId">>),
ok = emqx_broker:subscribe(lists:nth(2, Topics), <<"clientId">>),
ok = emqx_broker:subscribe(lists:nth(3, Topics), <<"clientId">>),
Topics1 = emqx_broker:topics(),
?assertEqual(true, lists:foldl(fun(Topic, Acc) ->
case lists:member(Topic, Topics1) of
true -> Acc;
false -> false
end
end, true, Topics)),
emqx_broker:unsubscribe(lists:nth(1, Topics)),
emqx_broker:unsubscribe(lists:nth(2, Topics)),
emqx_broker:unsubscribe(lists:nth(3, Topics)).
t_subscribers(_) ->
emqx_broker:subscribe(<<"topic">>, <<"clientid">>),
?assertEqual([self()], emqx_broker:subscribers(<<"topic">>)),
emqx_broker:unsubscribe(<<"topic">>).
t_subscriptions(_) ->
emqx_broker:subscribe(<<"topic">>, <<"clientid">>, #{qos => 1}),
?assertEqual(#{qos => 1, subid => <<"clientid">>},
proplists:get_value(<<"topic">>, emqx_broker:subscriptions(self()))),
?assertEqual(#{qos => 1, subid => <<"clientid">>},
proplists:get_value(<<"topic">>, emqx_broker:subscriptions(<<"clientid">>))),
emqx_broker:unsubscribe(<<"topic">>).
t_sub_pub(_) ->
ok = emqx_broker:subscribe(<<"topic">>),
ct:sleep(10),
emqx_broker:safe_publish(emqx_message:make(ct, <<"topic">>, <<"hello">>)),
?assert(
receive {deliver, <<"a/b/c">>, _ } ->
receive
{deliver, <<"topic">>, #message{payload = <<"hello">>}} ->
true;
P ->
ct:log("Receive Message: ~p~n",[P])
_ ->
false
after 100 ->
false
end).
t_nosub_pub(_) ->
?assertEqual(0, emqx_metrics:val('messages.dropped')),
emqx_broker:publish(emqx_message:make(ct, <<"topic">>, <<"hello">>)),
?assertEqual(1, emqx_metrics:val('messages.dropped')).
t_shared_subscribe(_) ->
emqx_broker:subscribe(<<"topic">>, <<"clientid">>, #{share => <<"group">>}),
ct:sleep(10),
emqx_broker:safe_publish(emqx_message:make(ct, <<"topic">>, <<"hello">>)),
?assert(receive
{deliver, <<"topic">>, #message{payload = <<"hello">>}} ->
true;
Msg ->
ct:pal("Msg: ~p", [Msg]),
false
after 100 ->
false
end),
emqx_broker:unsubscribe(<<"$share/group/topic">>).
t_shared_subscribe_2(_) ->
{ok, ConnPid} = emqtt:start_link([{clean_start, true}, {clientid, <<"clientid">>}]),
{ok, _} = emqtt:connect(ConnPid),
{ok, _, [0]} = emqtt:subscribe(ConnPid, <<"$share/group/topic">>, 0),
{ok, ConnPid2} = emqtt:start_link([{clean_start, true}, {clientid, <<"clientid2">>}]),
{ok, _} = emqtt:connect(ConnPid2),
{ok, _, [0]} = emqtt:subscribe(ConnPid2, <<"$share/group2/topic">>, 0),
ct:sleep(10),
ok = emqtt:publish(ConnPid, <<"topic">>, <<"hello">>, 0),
Msgs = recv_msgs(2),
?assertEqual(2, length(Msgs)),
?assertEqual(true, lists:foldl(fun(#{payload := <<"hello">>, topic := <<"topic">>}, Acc) ->
Acc;
(_, _) ->
false
end, true, Msgs)),
emqtt:disconnect(ConnPid),
emqtt:disconnect(ConnPid2).
t_shared_subscribe_3(_) ->
{ok, ConnPid} = emqtt:start_link([{clean_start, true}, {clientid, <<"clientid">>}]),
{ok, _} = emqtt:connect(ConnPid),
{ok, _, [0]} = emqtt:subscribe(ConnPid, <<"$share/group/topic">>, 0),
{ok, ConnPid2} = emqtt:start_link([{clean_start, true}, {clientid, <<"clientid2">>}]),
{ok, _} = emqtt:connect(ConnPid2),
{ok, _, [0]} = emqtt:subscribe(ConnPid2, <<"$share/group/topic">>, 0),
ct:sleep(10),
ok = emqtt:publish(ConnPid, <<"topic">>, <<"hello">>, 0),
Msgs = recv_msgs(2),
?assertEqual(1, length(Msgs)),
emqtt:disconnect(ConnPid),
emqtt:disconnect(ConnPid2).
t_shard(_) ->
ok = meck:new(emqx_broker_helper, [passthrough, no_history]),
ok = meck:expect(emqx_broker_helper, get_sub_shard, fun(_, _) -> 1 end),
emqx_broker:subscribe(<<"topic">>, <<"clientid">>),
ct:sleep(10),
emqx_broker:safe_publish(emqx_message:make(ct, <<"topic">>, <<"hello">>)),
?assert(
receive
{deliver, <<"topic">>, #message{payload = <<"hello">>}} ->
true;
_ ->
false
after 100 ->
false
end),
spawn(fun() ->
emqx_broker:subscribe(<<"a/b/c">>),
emqx_broker:subscribe(<<"c/d/e">>),
timer:sleep(10),
emqx_broker:unsubscribe(<<"a/b/c">>)
end),
timer:sleep(20),
emqx_broker:unsubscribe(<<"a/b/c">>).
t_shared_subscribe(_) ->
emqx_broker:subscribe(<<"$share/group2/topic2">>),
emqx_broker:subscribe(<<"$queue/topic3">>),
timer:sleep(10),
ct:pal("Share subscriptions: ~p",
[emqx_broker:subscriptions(self())]),
?assertEqual(2, length(emqx_broker:subscriptions(self()))),
emqx_broker:unsubscribe(<<"$share/group2/topic2">>),
emqx_broker:unsubscribe(<<"$queue/topic3">>),
?assertEqual(0, length(emqx_broker:subscriptions(self()))).
't_pubsub#'(_) ->
emqx_broker:subscribe(<<"a/#">>),
timer:sleep(10),
emqx_broker:publish(emqx_message:make(ct, <<"a/b/c">>, <<"hello">>)),
?assert(receive {deliver, <<"a/#">>, _} -> true after 100 -> false end),
emqx_broker:unsubscribe(<<"a/#">>).
't_pubsub+'(_) ->
emqx_broker:subscribe(<<"a/+/+">>),
timer:sleep(10), %% TODO: why sleep?
emqx_broker:publish(emqx_message:make(ct, <<"a/b/c">>, <<"hello">>)),
?assert(receive {deliver, <<"a/+/+">>, _} -> true after 100 -> false end),
emqx_broker:unsubscribe(<<"a/+/+">>).
%%--------------------------------------------------------------------
%% Metric Group
%%--------------------------------------------------------------------
inc_dec_metric(_) ->
emqx_metrics:inc('messages.retained', 10),
emqx_metrics:dec('messages.retained', 10).
%%--------------------------------------------------------------------
%% Stats Group
%%--------------------------------------------------------------------
set_get_stat(_) ->
emqx_stats:setstat('retained.max', 99),
?assertEqual(99, emqx_stats:getstat('retained.max')).
t_dispatch(_) ->
error('TODO').
t_subscriber_down(_) ->
error('TODO').
t_get_subopts(_) ->
error('TODO').
t_set_subopts(_) ->
error('TODO').
t_topics(_) ->
error('TODO').
ok = meck:unload(emqx_broker_helper).
t_stats_fun(_) ->
error('TODO').
?assertEqual(0, emqx_stats:getstat('subscribers.count')),
?assertEqual(0, emqx_stats:getstat('subscriptions.count')),
?assertEqual(0, emqx_stats:getstat('suboptions.count')),
ok = emqx_broker:subscribe(<<"topic">>, <<"clientid">>),
ok = emqx_broker:subscribe(<<"topic2">>, <<"clientid">>),
emqx_broker:stats_fun(),
ct:sleep(10),
?assertEqual(2, emqx_stats:getstat('subscribers.count')),
?assertEqual(2, emqx_stats:getstat('subscribers.max')),
?assertEqual(2, emqx_stats:getstat('subscriptions.count')),
?assertEqual(2, emqx_stats:getstat('subscriptions.max')),
?assertEqual(2, emqx_stats:getstat('suboptions.count')),
?assertEqual(2, emqx_stats:getstat('suboptions.max')).
t_init(_) ->
error('TODO').
recv_msgs(Count) ->
recv_msgs(Count, []).
t_handle_call(_) ->
error('TODO').
t_handle_cast(_) ->
error('TODO').
t_handle_info(_) ->
error('TODO').
t_terminate(_) ->
error('TODO').
t_code_change(_) ->
error('TODO').
t_safe_publish(_) ->
error('TODO').
t_subscribed(_) ->
error('TODO').
t_subscriptions(_) ->
error('TODO').
t_subscribers(_) ->
error('TODO').
t_unsubscribe(_) ->
error('TODO').
t_subscribe(_) ->
error('TODO').
recv_msgs(0, Msgs) ->
Msgs;
recv_msgs(Count, Msgs) ->
receive
{publish, Msg} ->
recv_msgs(Count-1, [Msg|Msgs]);
_Other -> recv_msgs(Count, Msgs)
after 100 ->
Msgs
end.

View File

@ -24,47 +24,44 @@
all() -> emqx_ct:all(?MODULE).
init_per_testcase(_TestCase, Config) ->
emqx_broker_helper:start_link(),
Config.
end_per_testcase(_TestCase, Config) ->
Config.
t_start_link(_) ->
error('TODO').
t_lookup_subid(_) ->
error('TODO').
t_create_seq(_) ->
error('TODO').
t_init(_) ->
error('TODO').
t_handle_call(_) ->
error('TODO').
t_handle_cast(_) ->
error('TODO').
t_handle_info(_) ->
error('TODO').
t_terminate(_) ->
error('TODO').
t_code_change(_) ->
error('TODO').
?assertEqual(undefined, emqx_broker_helper:lookup_subid(self())),
emqx_broker_helper:register_sub(self(), <<"clientid">>),
ct:sleep(10),
?assertEqual(<<"clientid">>, emqx_broker_helper:lookup_subid(self())).
t_lookup_subpid(_) ->
error('TODO').
t_reclaim_seq(_) ->
error('TODO').
t_get_sub_shard(_) ->
error('TODO').
?assertEqual(undefined, emqx_broker_helper:lookup_subpid(<<"clientid">>)),
emqx_broker_helper:register_sub(self(), <<"clientid">>),
ct:sleep(10),
?assertEqual(self(), emqx_broker_helper:lookup_subpid(<<"clientid">>)).
t_register_sub(_) ->
error('TODO').
ok = emqx_broker_helper:register_sub(self(), <<"clientid">>),
ct:sleep(10),
ok = emqx_broker_helper:register_sub(self(), <<"clientid">>),
try emqx_broker_helper:register_sub(self(), <<"clientid2">>) of
_ -> ct:fail(should_throw_error)
catch error:Reason ->
?assertEqual(Reason, subid_conflict)
end,
?assertEqual(self(), emqx_broker_helper:lookup_subpid(<<"clientid">>)).
t_shard_seq(_) ->
?assertEqual([], ets:lookup(emqx_subseq, <<"topic">>)),
emqx_broker_helper:create_seq(<<"topic">>),
?assertEqual([{<<"topic">>, 1}], ets:lookup(emqx_subseq, <<"topic">>)),
emqx_broker_helper:reclaim_seq(<<"topic">>),
?assertEqual([], ets:lookup(emqx_subseq, <<"topic">>)).
t_shards_num(_) ->
?assertEqual(emqx_vm:schedulers() * 32, emqx_broker_helper:shards_num()).
t_get_sub_shard(_) ->
?assertEqual(0, emqx_broker_helper:get_sub_shard(self(), <<"topic">>)).

View File

@ -94,18 +94,9 @@ t_chan_info(_) ->
t_chan_attrs(_) ->
#{conn_state := connected} = emqx_channel:attrs(channel()).
t_chan_stats(_) ->
[] = emqx_channel:stats(channel()).
t_chan_caps(_) ->
Caps = emqx_channel:caps(channel()).
t_chan_recvd(_) ->
_Channel = emqx_channel:recvd(10, channel()).
t_chan_sent(_) ->
_Channel = emqx_channel:sent(10, channel()).
%%--------------------------------------------------------------------
%% Test cases for channel init
%%--------------------------------------------------------------------
@ -140,86 +131,86 @@ t_handle_in_qos0_publish(_) ->
ok = meck:expect(emqx_broker, publish, fun(_) -> ok end),
Channel = channel(#{conn_state => connected}),
Publish = ?PUBLISH_PACKET(?QOS_0, <<"topic">>, undefined, <<"payload">>),
{ok, NChannel} = emqx_channel:handle_in(Publish, Channel),
?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, NChannel)).
{ok, _NChannel} = emqx_channel:handle_in(Publish, Channel).
% ?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, NChannel)).
t_handle_in_qos1_publish(_) ->
ok = meck:expect(emqx_broker, publish, fun(_) -> ok end),
Channel = channel(#{conn_state => connected}),
Publish = ?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, <<"payload">>),
{ok, ?PUBACK_PACKET(1, RC), NChannel} = emqx_channel:handle_in(Publish, Channel),
?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)),
?assertEqual(#{publish_in => 1, puback_out => 1}, emqx_channel:info(pub_stats, NChannel)).
{ok, ?PUBACK_PACKET(1, RC), _NChannel} = emqx_channel:handle_in(Publish, Channel),
?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)).
% ?assertEqual(#{publish_in => 1, puback_out => 1}, emqx_channel:info(pub_stats, NChannel)).
t_handle_in_qos2_publish(_) ->
ok = meck:expect(emqx_session, publish, fun(_, _Msg, Session) -> {ok, [], Session} end),
ok = meck:expect(emqx_session, info, fun(awaiting_rel_timeout, _Session) -> 300000 end),
Channel = channel(#{conn_state => connected}),
Publish = ?PUBLISH_PACKET(?QOS_2, <<"topic">>, 1, <<"payload">>),
{ok, ?PUBREC_PACKET(1, RC), NChannel} = emqx_channel:handle_in(Publish, Channel),
?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)),
?assertEqual(#{publish_in => 1, pubrec_out => 1}, emqx_channel:info(pub_stats, NChannel)).
{ok, ?PUBREC_PACKET(1, RC), _NChannel} = emqx_channel:handle_in(Publish, Channel),
?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)).
% ?assertEqual(#{publish_in => 1, pubrec_out => 1}, emqx_channel:info(pub_stats, NChannel)).
t_handle_in_puback_ok(_) ->
Msg = emqx_message:make(<<"t">>, <<"payload">>),
ok = meck:expect(emqx_session, puback,
fun(PacketId, Session) -> {ok, Msg, Session} end),
Channel = channel(#{conn_state => connected}),
{ok, NChannel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), Channel),
?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, NChannel)).
{ok, _NChannel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), Channel).
% ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, NChannel)).
t_handle_in_puback_id_in_use(_) ->
ok = meck:expect(emqx_session, puback,
fun(_, _Session) ->
{error, ?RC_PACKET_IDENTIFIER_IN_USE}
end),
{ok, Channel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), channel()),
?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel)).
{ok, _Channel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), channel()).
% ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel)).
t_handle_in_puback_id_not_found(_) ->
ok = meck:expect(emqx_session, puback,
fun(_, _Session) ->
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND}
end),
{ok, Channel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), channel()),
?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel)).
{ok, _Channel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), channel()).
% ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel)).
t_handle_in_pubrec_ok(_) ->
Msg = emqx_message:make(test,?QOS_2, <<"t">>, <<"payload">>),
ok = meck:expect(emqx_session, pubrec, fun(_, Session) -> {ok, Msg, Session} end),
Channel = channel(#{conn_state => connected}),
{ok, ?PUBREL_PACKET(1, ?RC_SUCCESS), Channel1}
= emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel),
?assertEqual(#{pubrec_in => 1, pubrel_out => 1},
emqx_channel:info(pub_stats, Channel1)).
{ok, ?PUBREL_PACKET(1, ?RC_SUCCESS), _Channel1}
= emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel).
% ?assertEqual(#{pubrec_in => 1, pubrel_out => 1},
% emqx_channel:info(pub_stats, Channel1)).
t_handle_in_pubrec_id_in_use(_) ->
ok = meck:expect(emqx_session, pubrec,
fun(_, Session) ->
{error, ?RC_PACKET_IDENTIFIER_IN_USE}
end),
{ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_IN_USE), Channel}
= emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), channel()),
?assertEqual(#{pubrec_in => 1, pubrel_out => 1},
emqx_channel:info(pub_stats, Channel)).
{ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_IN_USE), _Channel}
= emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), channel()).
% ?assertEqual(#{pubrec_in => 1, pubrel_out => 1},
% emqx_channel:info(pub_stats, Channel)).
t_handle_in_pubrec_id_not_found(_) ->
ok = meck:expect(emqx_session, pubrec,
fun(_, Session) ->
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND}
end),
{ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel}
= emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), channel()),
?assertEqual(#{pubrec_in => 1, pubrel_out => 1},
emqx_channel:info(pub_stats, Channel)).
{ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), _Channel}
= emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), channel()).
% ?assertEqual(#{pubrec_in => 1, pubrel_out => 1},
% emqx_channel:info(pub_stats, Channel)).
t_handle_in_pubrel_ok(_) ->
ok = meck:expect(emqx_session, pubrel, fun(_, Session) -> {ok, Session} end),
Channel = channel(#{conn_state => connected}),
{ok, ?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel1}
= emqx_channel:handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel),
?assertEqual(#{pubrel_in => 1, pubcomp_out => 1},
emqx_channel:info(pub_stats, Channel1)).
{ok, ?PUBCOMP_PACKET(1, ?RC_SUCCESS), _Channel1}
= emqx_channel:handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel).
% ?assertEqual(#{pubrel_in => 1, pubcomp_out => 1},
% emqx_channel:info(pub_stats, Channel1)).
t_handle_in_pubrel_not_found_error(_) ->
ok = meck:expect(emqx_session, pubrel,
@ -231,8 +222,8 @@ t_handle_in_pubrel_not_found_error(_) ->
t_handle_in_pubcomp_ok(_) ->
ok = meck:expect(emqx_session, pubcomp, fun(_, Session) -> {ok, Session} end),
{ok, Channel} = emqx_channel:handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), channel()),
?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel)).
{ok, _Channel} = emqx_channel:handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), channel()).
% ?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel)).
t_handle_in_pubcomp_not_found_error(_) ->
ok = meck:expect(emqx_session, pubcomp,
@ -240,8 +231,8 @@ t_handle_in_pubcomp_not_found_error(_) ->
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND}
end),
Channel = channel(#{conn_state => connected}),
{ok, Channel1} = emqx_channel:handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel),
?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel1)).
{ok, _Channel1} = emqx_channel:handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel).
% ?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel1)).
t_handle_in_subscribe(_) ->
ok = meck:expect(emqx_session, subscribe,
@ -351,10 +342,10 @@ t_handle_out_publishes(_) ->
Channel = channel(#{conn_state => connected}),
Pub0 = {publish, undefined, emqx_message:make(<<"t">>, <<"qos0">>)},
Pub1 = {publish, 1, emqx_message:make(<<"c">>, ?QOS_1, <<"t">>, <<"qos1">>)},
{ok, {outgoing, Packets}, NChannel}
{ok, {outgoing, Packets}, _NChannel}
= emqx_channel:handle_out({publish, [Pub0, Pub1]}, Channel),
?assertEqual(2, length(Packets)),
?assertEqual(#{publish_out => 2}, emqx_channel:info(pub_stats, NChannel)).
?assertEqual(2, length(Packets)).
% ?assertEqual(#{publish_out => 2}, emqx_channel:info(pub_stats, NChannel)).
t_handle_out_publish(_) ->
Msg = emqx_message:make(<<"clientid">>, ?QOS_1, <<"t">>, <<"payload">>),
@ -378,28 +369,28 @@ t_handle_out_connack_failure(_) ->
t_handle_out_puback(_) ->
Channel = channel(#{conn_state => connected}),
{ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), NChannel}
= emqx_channel:handle_out(puback, {1, ?RC_SUCCESS}, Channel),
?assertEqual(#{puback_out => 1}, emqx_channel:info(pub_stats, NChannel)).
{ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), _NChannel}
= emqx_channel:handle_out(puback, {1, ?RC_SUCCESS}, Channel).
% ?assertEqual(#{puback_out => 1}, emqx_channel:info(pub_stats, NChannel)).
t_handle_out_pubrec(_) ->
Channel = channel(#{conn_state => connected}),
{ok, ?PUBREC_PACKET(1, ?RC_SUCCESS), NChannel}
= emqx_channel:handle_out(pubrec, {1, ?RC_SUCCESS}, Channel),
?assertEqual(#{pubrec_out => 1}, emqx_channel:info(pub_stats, NChannel)).
{ok, ?PUBREC_PACKET(1, ?RC_SUCCESS), _NChannel}
= emqx_channel:handle_out(pubrec, {1, ?RC_SUCCESS}, Channel).
% ?assertEqual(#{pubrec_out => 1}, emqx_channel:info(pub_stats, NChannel)).
t_handle_out_pubrel(_) ->
Channel = channel(#{conn_state => connected}),
{ok, ?PUBREL_PACKET(1), Channel1}
= emqx_channel:handle_out(pubrel, {1, ?RC_SUCCESS}, Channel),
{ok, ?PUBREL_PACKET(2, ?RC_SUCCESS), Channel2}
= emqx_channel:handle_out(pubrel, {2, ?RC_SUCCESS}, Channel1),
?assertEqual(#{pubrel_out => 2}, emqx_channel:info(pub_stats, Channel2)).
{ok, ?PUBREL_PACKET(2, ?RC_SUCCESS), _Channel2}
= emqx_channel:handle_out(pubrel, {2, ?RC_SUCCESS}, Channel1).
% ?assertEqual(#{pubrel_out => 2}, emqx_channel:info(pub_stats, Channel2)).
t_handle_out_pubcomp(_) ->
{ok, ?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel}
= emqx_channel:handle_out(pubcomp, {1, ?RC_SUCCESS}, channel()),
?assertEqual(#{pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel)).
{ok, ?PUBCOMP_PACKET(1, ?RC_SUCCESS), _Channel}
= emqx_channel:handle_out(pubcomp, {1, ?RC_SUCCESS}, channel()).
% ?assertEqual(#{pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel)).
t_handle_out_suback(_) ->
{ok, ?SUBACK_PACKET(1, [?QOS_2]), _Channel}

View File

@ -89,26 +89,26 @@ t_lock_clientid(_) ->
{true, _Nodes} = emqx_cm_locker:unlock(<<"clientid">>).
t_unregister_channel(_) ->
error('TODO').
% t_unregister_channel(_) ->
% error('TODO').
t_get_chan_attrs(_) ->
error('TODO').
% t_get_chan_attrs(_) ->
% error('TODO').
t_get_chan_stats(_) ->
error('TODO').
% t_get_chan_stats(_) ->
% error('TODO').
t_lookup_channels(_) ->
error('TODO').
% t_lookup_channels(_) ->
% error('TODO').
t_set_chan_stats(_) ->
error('TODO').
% t_set_chan_stats(_) ->
% error('TODO').
t_set_chan_attrs(_) ->
error('TODO').
% t_set_chan_attrs(_) ->
% error('TODO').
t_register_channel(_) ->
error('TODO').
% t_register_channel(_) ->
% error('TODO').
t_stats_fun(_) ->
error('TODO').
% t_stats_fun(_) ->
% error('TODO').

View File

@ -29,15 +29,15 @@ init_per_testcase(_TestCase, Config) ->
end_per_testcase(_TestCase, Config) ->
Config.
t_start_link(_) ->
error('TODO').
% t_start_link(_) ->
% error('TODO').
t_trans(_) ->
error('TODO').
% t_trans(_) ->
% error('TODO').
t_lock(_) ->
error('TODO').
% t_lock(_) ->
% error('TODO').
t_unlock(_) ->
error('TODO').
% t_unlock(_) ->
% error('TODO').

View File

@ -21,44 +21,57 @@
-include_lib("eunit/include/eunit.hrl").
%%--------------------------------------------------------------------
%% CT callbacks
%%--------------------------------------------------------------------
all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) ->
emqx_ct_helpers:boot_modules(all),
emqx_ct_helpers:start_apps([]),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
init_per_testcase(_TestCase, Config) ->
Config.
end_per_testcase(_TestCase, Config) ->
Config.
t_start_link(_) ->
error('TODO').
t_init(_) ->
error('TODO').
t_handle_call(_) ->
error('TODO').
t_handle_cast(_) ->
error('TODO').
t_handle_info(_) ->
error('TODO').
t_terminate(_) ->
error('TODO').
t_code_change(_) ->
error('TODO').
t_lookup_channels(_) ->
error('TODO').
t_is_enabled(_) ->
error('TODO').
application:set_env(emqx, enable_channel_registry, false),
?assertEqual(false, emqx_cm_registry:is_enabled()),
application:set_env(emqx, enable_channel_registry, true),
?assertEqual(true, emqx_cm_registry:is_enabled()).
t_unregister_channel(_) ->
error('TODO').
t_register_unregister_channel(_) ->
ClientId = <<"clientid">>,
application:set_env(emqx, enable_channel_registry, false),
emqx_cm_registry:register_channel(ClientId),
?assertEqual([], emqx_cm_registry:lookup_channels(ClientId)),
t_register_channel(_) ->
error('TODO').
application:set_env(emqx, enable_channel_registry, true),
emqx_cm_registry:register_channel(ClientId),
?assertEqual([self()], emqx_cm_registry:lookup_channels(ClientId)),
application:set_env(emqx, enable_channel_registry, false),
emqx_cm_registry:unregister_channel(ClientId),
?assertEqual([self()], emqx_cm_registry:lookup_channels(ClientId)),
application:set_env(emqx, enable_channel_registry, true),
emqx_cm_registry:unregister_channel(ClientId),
?assertEqual([], emqx_cm_registry:lookup_channels(ClientId)).
t_cleanup_channels(_) ->
ClientId = <<"clientid">>,
ClientId2 = <<"clientid2">>,
emqx_cm_registry:register_channel(ClientId),
emqx_cm_registry:register_channel(ClientId2),
?assertEqual([self()], emqx_cm_registry:lookup_channels(ClientId)),
emqx_cm_registry ! {membership, {mnesia, down, node()}},
ct:sleep(100),
?assertEqual([], emqx_cm_registry:lookup_channels(ClientId)),
?assertEqual([], emqx_cm_registry:lookup_channels(ClientId2)).

View File

@ -27,7 +27,14 @@
send_pend
]).
all() -> emqx_ct:all(?MODULE).
all() -> emqx_ct:all(?MODULE) ++ [{group, real_client}].
groups() ->
[{real_client, [non_parallel_tests],
[
g_get_conn_stats,
g_handle_sock_passive
]}].
%%--------------------------------------------------------------------
%% CT callbacks
@ -39,6 +46,16 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
ok.
init_per_group(real_client, Config) ->
emqx_ct_helpers:boot_modules(all),
emqx_ct_helpers:start_apps([]),
Config;
init_per_group(_, Config) -> Config.
end_per_group(real_client, _Config) ->
emqx_ct_helpers:stop_apps([]);
end_per_group(_, Config) -> Config.
init_per_testcase(_TestCase, Config) ->
%% Meck Transport
ok = meck:new(emqx_transport, [non_strict, passthrough, no_history]),
@ -95,22 +112,19 @@ t_start_link_exit_on_activate(_) ->
t_get_conn_info(_) ->
with_connection(fun(CPid) ->
#{sockinfo := SockInfo} = emqx_connection:info(CPid),
?assertEqual(#{active_n => 100,
peername => {{127,0,0,1},3456},
pub_limit => undefined,
rate_limit => undefined,
sockname => {{127,0,0,1},1883},
sockstate => running,
socktype => tcp}, SockInfo)
?assertEqual(#{active_n => 100,limiter => undefined,
peername => {{127,0,0,1},3456},
sockname => {{127,0,0,1},1883},
sockstate => running,
socktype => tcp}, SockInfo)
end).
t_get_conn_stats(_) ->
with_connection(fun(CPid) ->
g_get_conn_stats(_) ->
with_client(fun(CPid) ->
Stats = emqx_connection:stats(CPid),
lists:foreach(fun(Key) ->
0 = proplists:get_value(Key, Stats)
end, ?STATS_KYES)
end).
ct:pal("==== stats: ~p", [Stats]),
[?assert(proplists:get_value(Key, Stats) >= 0) || Key <- ?STATS_KYES]
end, []).
t_handle_call_discard(_) ->
with_connection(fun(CPid) ->
@ -190,8 +204,8 @@ t_handle_sock_error(_) ->
trap_exit(CPid, {shutdown, econnreset})
end, #{trap_exit => true}).
t_handle_sock_passive(_) ->
with_connection(fun(CPid) -> CPid ! {tcp_passive, sock} end).
g_handle_sock_passive(_) ->
with_client(fun(CPid) -> CPid ! {tcp_passive, sock} end, []).
t_handle_sock_activate(_) ->
with_connection(fun(CPid) -> CPid ! activate_socket end).
@ -313,6 +327,18 @@ with_connection(TestFun, Options) ->
TrapExit orelse emqx_connection:stop(CPid),
ok.
with_client(TestFun, _Options) ->
ClientId = <<"t_conn">>,
{ok, C} = emqtt:start_link([{clientid, ClientId}]),
{ok, _} = emqtt:connect(C),
timer:sleep(50),
case emqx_cm:lookup_channels(ClientId) of
[] -> ct:fail({client_not_started, ClientId});
[ChanPid] ->
TestFun(ChanPid),
emqtt:stop(C)
end.
trap_exit(Pid, Reason) ->
receive
{'EXIT', Pid, Reason} -> ok;

View File

@ -38,14 +38,6 @@ set_special_configs(_App) -> ok.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]),
ok.
t_check(_) ->
error('TODO').
t_detect(_) ->
error('TODO').
t_detect_check(_) ->
ClientInfo = #{zone => external,

View File

@ -39,26 +39,3 @@ t_guid_hexstr(_) ->
t_guid_base62(_) ->
Guid = emqx_guid:gen(),
?assertEqual(Guid, emqx_guid:from_base62(emqx_guid:to_base62(Guid))).
t_new(_) ->
error('TODO').
t_timestamp(_) ->
error('TODO').
t_to_hexstr(_) ->
error('TODO').
t_from_hexstr(_) ->
error('TODO').
t_from_base62(_) ->
error('TODO').
t_to_base62(_) ->
error('TODO').
t_gen(_) ->
error('TODO').

View File

@ -23,25 +23,21 @@
all() -> emqx_ct:all(?MODULE).
% t_lookup(_) ->
% error('TODO').
% t_run_fold(_) ->
% error('TODO').
t_lookup(_) ->
error('TODO').
% t_run(_) ->
% error('TODO').
% t_del(_) ->
% error('TODO').
t_run_fold(_) ->
error('TODO').
t_run(_) ->
error('TODO').
t_del(_) ->
error('TODO').
t_add(_) ->
error('TODO').
% t_add(_) ->
% error('TODO').
t_add_del_hook(_) ->
{ok, _} = emqx_hooks:start_link(),
ok = emqx:hook(test_hook, fun ?MODULE:hook_fun1/1, []),

View File

@ -23,10 +23,6 @@
-include_lib("emqx_ct_helpers/include/emqx_ct.hrl").
all() -> emqx_ct:all(?MODULE).
t_new(_) ->
error('TODO').
t_contain(_) ->
Inflight = emqx_inflight:insert(k, v, emqx_inflight:new()),
@ -94,12 +90,5 @@ t_window(_) ->
a, 1, emqx_inflight:new(2))),
?assertEqual([a, b], emqx_inflight:window(Inflight)).
t_to_list(_) ->
error('TODO').
t_size(_) ->
error('TODO').
t_max_size(_) ->
error('TODO').
% t_to_list(_) ->
% error('TODO').

View File

@ -23,13 +23,6 @@
all() -> emqx_ct:all(?MODULE).
t_init(_) ->
error('TODO').
t_info(_) ->
error('TODO').
t_check(_) ->
Keepalive = emqx_keepalive:init(60),
?assertEqual(60, emqx_keepalive:info(interval, Keepalive)),

View File

@ -37,6 +37,7 @@ end_per_suite(_Config) ->
t_start_stop_listeners(_) ->
ok = emqx_listeners:start(),
{error, _} = emqx_listeners:start_listener({ws,{"127.0.0.1", 8083}, []}),
ok = emqx_listeners:stop().
t_restart_listeners(_) ->
@ -89,23 +90,4 @@ get_base_dir(Module) ->
get_base_dir() ->
get_base_dir(?MODULE).
t_start_listener(_) ->
error('TODO').
t_restart(_) ->
error('TODO').
t_restart_listener(_) ->
error('TODO').
t_stop_listener(_) ->
error('TODO').
t_stop(_) ->
error('TODO').
t_start(_) ->
error('TODO').

View File

@ -21,6 +21,22 @@
-include_lib("eunit/include/eunit.hrl").
-define(LOGGER, emqx_logger).
-define(a, "a").
-define(PARSE_TRANS_TEST_CODE,
"-module(mytest).\n"
"-logger_header(\"[MyTest]\").\n"
"-export([run/0]).\n"
"-compile({parse_transform, logger_header}).\n"
"run() -> '$logger_header'().").
-define(PARSE_TRANS_TEST_CODE2,
"-module(mytest).\n"
"-export([run/0]).\n"
"-compile({parse_transform, logger_header}).\n"
"run() -> '$logger_header'().").
all() -> emqx_ct:all(?MODULE).
init_per_testcase(_TestCase, Config) ->
@ -30,47 +46,103 @@ end_per_testcase(_TestCase, Config) ->
Config.
t_debug(_) ->
error('TODO').
?assertEqual(ok, ?LOGGER:debug("for_test")),
?assertEqual(ok, ?LOGGER:debug("for_test", [])),
?assertEqual(ok, ?LOGGER:debug(#{pid => self()}, "for_test", [])).
t_info(_) ->
error('TODO').
?assertEqual(ok, ?LOGGER:info("for_test")),
?assertEqual(ok, ?LOGGER:info("for_test", [])),
?assertEqual(ok, ?LOGGER:info(#{pid => self()}, "for_test", [])).
t_warning(_) ->
error('TODO').
?assertEqual(ok, ?LOGGER:warning("for_test")),
?assertEqual(ok, ?LOGGER:warning("for_test", [])),
?assertEqual(ok, ?LOGGER:warning(#{pid => self()}, "for_test", [])).
t_error(_) ->
error('TODO').
?assertEqual(ok, ?LOGGER:error("for_test")),
?assertEqual(ok, ?LOGGER:error("for_test", [])),
?assertEqual(ok, ?LOGGER:error(#{pid => self()}, "for_test", [])).
t_critical(_) ->
error('TODO').
?assertEqual(ok, ?LOGGER:critical("for_test")),
?assertEqual(ok, ?LOGGER:critical("for_test", [])),
?assertEqual(ok, ?LOGGER:critical(#{pid => self()}, "for_test", [])).
t_set_proc_metadata(_) ->
error('TODO').
?assertEqual(ok, ?LOGGER:set_proc_metadata(#{pid => self()})).
t_get_primary_log_level(_) ->
error('TODO').
t_set_primary_log_level(_) ->
error('TODO').
t_primary_log_level(_) ->
?assertEqual(ok, ?LOGGER:set_primary_log_level(debug)),
?assertEqual(debug, ?LOGGER:get_primary_log_level()).
t_get_log_handlers(_) ->
error('TODO').
ok = logger:add_handler(logger_std_h_for_test, logger_std_h, #{config => #{type => file, file => "logger_std_h_for_test"}}),
ok = logger:add_handler(logger_disk_log_h_for_test, logger_disk_log_h, #{config => #{file => "logger_disk_log_h_for_test"}}),
?assertMatch([_|_], ?LOGGER:get_log_handlers()).
t_get_log_handler(_) ->
error('TODO').
[{HandlerId, _, _} | _ ] = ?LOGGER:get_log_handlers(),
?assertMatch({HandlerId, _, _}, ?LOGGER:get_log_handler(HandlerId)).
t_set_log_handler_level(_) ->
error('TODO').
[{HandlerId, _, _} | _ ] = ?LOGGER:get_log_handlers(),
Level = debug,
?LOGGER:set_log_handler_level(HandlerId, Level),
?assertMatch({HandlerId, Level, _}, ?LOGGER:get_log_handler(HandlerId)).
t_set_log_level(_) ->
error('TODO').
?assertMatch({error, _Error}, ?LOGGER:set_log_level(for_test)),
?assertEqual(ok, ?LOGGER:set_log_level(debug)).
t_set_all_log_handlers_level(_) ->
?assertMatch({error, _Error}, ?LOGGER:set_all_log_handlers_level(for_test)).
t_parse_transform(_) ->
error('TODO').
{ok, Toks, _EndLine} = erl_scan:string(?PARSE_TRANS_TEST_CODE),
FormToks = split_toks_at_dot(Toks),
Forms = [case erl_parse:parse_form(Ts) of
{ok, Form} ->
Form;
{error, Reason} ->
erlang:error({parse_form_error, Ts, Reason})
end
|| Ts <- FormToks],
%ct:log("=====: ~p", [Forms]),
AST1 = emqx_logger:parse_transform(Forms, []),
%ct:log("=====: ~p", [AST1]),
?assertNotEqual(false, lists:keyfind('$logger_header', 3, AST1)).
t_parse_transform_empty_header(_) ->
{ok, Toks, _EndLine} = erl_scan:string(?PARSE_TRANS_TEST_CODE2),
FormToks = split_toks_at_dot(Toks),
Forms = [case erl_parse:parse_form(Ts) of
{ok, Form} ->
Form;
{error, Reason} ->
erlang:error({parse_form_error, Ts, Reason})
end
|| Ts <- FormToks],
%ct:log("=====: ~p", [Forms]),
AST2 = emqx_logger:parse_transform(Forms++[{eof, 15}], []),
%ct:log("=====: ~p", [AST2]),
?assertNotEqual(false, lists:keyfind('$logger_header', 3, AST2)).
t_set_metadata_peername(_) ->
error('TODO').
?assertEqual(ok, ?LOGGER:set_metadata_peername("for_test")).
t_set_metadata_clientid(_) ->
error('TODO').
?assertEqual(ok, ?LOGGER:set_metadata_clientid(<<>>)),
?assertEqual(ok, ?LOGGER:set_metadata_clientid("for_test")).
split_toks_at_dot(AllToks) ->
case lists:splitwith(fun is_no_dot/1, AllToks) of
{Toks, [{dot,_}=Dot]} -> [Toks ++ [Dot]];
{Toks, [{dot,_}=Dot | Tl]} -> [Toks ++ [Dot] | split_toks_at_dot(Tl)]
end.
is_no_dot({dot,_}) -> false;
is_no_dot(_) -> true.

View File

@ -91,9 +91,6 @@ t_undefined_headers(_) ->
?assertEqual(1, emqx_message:get_header(a, Msg1)),
Msg2 = emqx_message:set_header(c, 3, Msg),
?assertEqual(3, emqx_message:get_header(c, Msg2)).
t_remove_header(_) ->
error('TODO').
t_format(_) ->
Msg = emqx_message:make(<<"clientid">>, <<"topic">>, <<"payload">>),
@ -118,9 +115,8 @@ t_is_expired(_) ->
Msg2 = emqx_message:update_expiry(Msg1),
?assertEqual(1, emqx_message:get_header('Message-Expiry-Interval', Msg2)).
t_to_list(_) ->
error('TODO').
% t_to_list(_) ->
% error('TODO').
t_to_packet(_) ->
Pkt = #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH,
@ -146,16 +142,3 @@ t_to_map(_) ->
{timestamp, emqx_message:timestamp(Msg)}],
?assertEqual(List, emqx_message:to_list(Msg)),
?assertEqual(maps:from_list(List), emqx_message:to_map(Msg)).
t_update_expiry(_) ->
error('TODO').
t_set_header(_) ->
error('TODO').
t_set_flag(_) ->
error('TODO').
t_set_headers(_) ->
error('TODO').

View File

@ -24,24 +24,10 @@
all() -> emqx_ct:all(?MODULE).
t_val(_) ->
error('TODO').
t_dec(_) ->
error('TODO').
t_set(_) ->
error('TODO').
t_commit(_) ->
error('TODO').
t_inc(_) ->
error('TODO').
t_new(_) ->
with_metrics_server(
fun() ->
ok = emqx_metrics:new('metrics.test'),
ok = emqx_metrics:new('metrics.test'),
0 = emqx_metrics:val('metrics.test'),
ok = emqx_metrics:inc('metrics.test'),
@ -86,16 +72,70 @@ t_inc_recv(_) ->
with_metrics_server(
fun() ->
ok = emqx_metrics:inc_recv(?PACKET(?CONNECT)),
?assertEqual(1, emqx_metrics:val('packets.received')),
?assertEqual(1, emqx_metrics:val('packets.connect.received'))
ok = emqx_metrics:inc_recv(?PUBLISH_PACKET(0, 0)),
ok = emqx_metrics:inc_recv(?PUBLISH_PACKET(1, 0)),
ok = emqx_metrics:inc_recv(?PUBLISH_PACKET(2, 0)),
ok = emqx_metrics:inc_recv(?PUBLISH_PACKET(3, 0)),
ok = emqx_metrics:inc_recv(?PACKET(?PUBACK)),
ok = emqx_metrics:inc_recv(?PACKET(?PUBREC)),
ok = emqx_metrics:inc_recv(?PACKET(?PUBREL)),
ok = emqx_metrics:inc_recv(?PACKET(?PUBCOMP)),
ok = emqx_metrics:inc_recv(?PACKET(?SUBSCRIBE)),
ok = emqx_metrics:inc_recv(?PACKET(?UNSUBSCRIBE)),
ok = emqx_metrics:inc_recv(?PACKET(?PINGREQ)),
ok = emqx_metrics:inc_recv(?PACKET(?DISCONNECT)),
ok = emqx_metrics:inc_recv(?PACKET(?AUTH)),
ok = emqx_metrics:inc_recv(?PACKET(?RESERVED)),
?assertEqual(15, emqx_metrics:val('packets.received')),
?assertEqual(1, emqx_metrics:val('packets.connect.received')),
?assertEqual(4, emqx_metrics:val('messages.received')),
?assertEqual(1, emqx_metrics:val('messages.qos0.received')),
?assertEqual(1, emqx_metrics:val('messages.qos1.received')),
?assertEqual(1, emqx_metrics:val('messages.qos2.received')),
?assertEqual(4, emqx_metrics:val('packets.publish.received')),
?assertEqual(1, emqx_metrics:val('packets.puback.received')),
?assertEqual(1, emqx_metrics:val('packets.pubrec.received')),
?assertEqual(1, emqx_metrics:val('packets.pubrel.received')),
?assertEqual(1, emqx_metrics:val('packets.pubcomp.received')),
?assertEqual(1, emqx_metrics:val('packets.subscribe.received')),
?assertEqual(1, emqx_metrics:val('packets.unsubscribe.received')),
?assertEqual(1, emqx_metrics:val('packets.pingreq.received')),
?assertEqual(1, emqx_metrics:val('packets.disconnect.received')),
?assertEqual(1, emqx_metrics:val('packets.auth.received'))
end).
t_inc_sent(_) ->
with_metrics_server(
fun() ->
ok = emqx_metrics:inc_sent(?CONNACK_PACKET(0)),
?assertEqual(1, emqx_metrics:val('packets.sent')),
?assertEqual(1, emqx_metrics:val('packets.connack.sent'))
ok = emqx_metrics:inc_sent(?PUBLISH_PACKET(0, 0)),
ok = emqx_metrics:inc_sent(?PUBLISH_PACKET(1, 0)),
ok = emqx_metrics:inc_sent(?PUBLISH_PACKET(2, 0)),
ok = emqx_metrics:inc_sent(?PUBACK_PACKET(0, 0)),
ok = emqx_metrics:inc_sent(?PUBREC_PACKET(3, 0)),
ok = emqx_metrics:inc_sent(?PACKET(?PUBREL)),
ok = emqx_metrics:inc_sent(?PACKET(?PUBCOMP)),
ok = emqx_metrics:inc_sent(?PACKET(?SUBACK)),
ok = emqx_metrics:inc_sent(?PACKET(?UNSUBACK)),
ok = emqx_metrics:inc_sent(?PACKET(?PINGRESP)),
ok = emqx_metrics:inc_sent(?PACKET(?DISCONNECT)),
ok = emqx_metrics:inc_sent(?PACKET(?AUTH)),
?assertEqual(13, emqx_metrics:val('packets.sent')),
?assertEqual(1, emqx_metrics:val('packets.connack.sent')),
?assertEqual(3, emqx_metrics:val('messages.sent')),
?assertEqual(1, emqx_metrics:val('messages.qos0.sent')),
?assertEqual(1, emqx_metrics:val('messages.qos1.sent')),
?assertEqual(1, emqx_metrics:val('messages.qos2.sent')),
?assertEqual(3, emqx_metrics:val('packets.publish.sent')),
?assertEqual(1, emqx_metrics:val('packets.puback.sent')),
?assertEqual(1, emqx_metrics:val('packets.pubrec.sent')),
?assertEqual(1, emqx_metrics:val('packets.pubrel.sent')),
?assertEqual(1, emqx_metrics:val('packets.pubcomp.sent')),
?assertEqual(1, emqx_metrics:val('packets.suback.sent')),
?assertEqual(1, emqx_metrics:val('packets.unsuback.sent')),
?assertEqual(1, emqx_metrics:val('packets.pingresp.sent')),
?assertEqual(1, emqx_metrics:val('packets.disconnect.sent')),
?assertEqual(1, emqx_metrics:val('packets.auth.sent'))
end).
t_trans(_) ->

View File

@ -64,7 +64,10 @@ t_pipeline(_) ->
fun(_I, St) -> {ok, St+1} end,
fun(I, St) -> {ok, I+1, St+1} end,
fun(I, St) -> {ok, I*2, St*2} end],
?assertEqual({ok, 4, 6}, emqx_misc:pipeline(Funs, 1, 1)).
?assertEqual({ok, 4, 6}, emqx_misc:pipeline(Funs, 1, 1)),
?assertEqual({error, undefined, 1}, emqx_misc:pipeline([fun(_I) -> {error, undefined} end], 1, 1)),
?assertEqual({error, undefined, 2}, emqx_misc:pipeline([fun(_I, _St) -> {error, undefined, 2} end], 1, 1)),
?assertEqual({error, undefined, 1}, emqx_misc:pipeline([fun(_I, _St) -> erlang:error(undefined) end], 1, 1)).
t_start_timer(_) ->
TRef = emqx_misc:start_timer(1, tmsg),
@ -75,7 +78,8 @@ t_start_timer(_) ->
t_cancel_timer(_) ->
Timer = emqx_misc:start_timer(0, foo),
ok = emqx_misc:cancel_timer(Timer),
?assertEqual([], drain()).
?assertEqual([], drain()),
ok = emqx_misc:cancel_timer(undefined).
t_proc_name(_) ->
?assertEqual(emqx_pool_1, emqx_misc:proc_name(emqx_pool, 1)).
@ -84,7 +88,11 @@ t_proc_stats(_) ->
Pid1 = spawn(fun() -> exit(normal) end),
timer:sleep(10),
?assertEqual([], emqx_misc:proc_stats(Pid1)),
Pid2 = spawn(fun() -> timer:sleep(100) end),
Pid2 = spawn(fun() ->
?assertMatch([{mailbox_len, 0}|_], emqx_misc:proc_stats()),
timer:sleep(200)
end),
timer:sleep(10),
Pid2 ! msg,
timer:sleep(10),
?assertMatch([{mailbox_len, 1}|_], emqx_misc:proc_stats(Pid2)).
@ -100,7 +108,24 @@ t_drain_down(_) ->
{Pid1, _Ref1} = erlang:spawn_monitor(fun() -> ok end),
{Pid2, _Ref2} = erlang:spawn_monitor(fun() -> ok end),
timer:sleep(100),
?assertEqual([Pid1, Pid2], emqx_misc:drain_down(2)).
?assertEqual([Pid1, Pid2], emqx_misc:drain_down(2)),
?assertEqual([], emqx_misc:drain_down(1)).
t_index_of(_) ->
try emqx_misc:index_of(a, []) of
_ -> ct:fail(should_throw_error)
catch error:Reason ->
?assertEqual(badarg, Reason)
end,
?assertEqual(3, emqx_misc:index_of(a, [b, c, a, e, f])).
t_check(_) ->
Policy = #{message_queue_len => 10,
max_heap_size => 1024 * 1024 * 8},
[self() ! {msg, I} || I <- lists:seq(1, 5)],
?assertEqual(ok, emqx_misc:check_oom(Policy)),
[self() ! {msg, I} || I <- lists:seq(1, 6)],
?assertEqual({shutdown, message_queue_too_long}, emqx_misc:check_oom(Policy)).
drain() ->
drain([]).

View File

@ -29,18 +29,18 @@ init_per_testcase(_TestCase, Config) ->
end_per_testcase(_TestCase, Config) ->
Config.
t_load(_) ->
error('TODO').
% t_load(_) ->
% error('TODO').
t_unload(_) ->
error('TODO').
% t_unload(_) ->
% error('TODO').
t_all_rules(_) ->
error('TODO').
% t_all_rules(_) ->
% error('TODO').
t_check_acl(_) ->
error('TODO').
% t_check_acl(_) ->
% error('TODO').
t_reload_acl(_) ->
error('TODO').
% t_reload_acl(_) ->
% error('TODO').

View File

@ -29,16 +29,16 @@ init_per_testcase(_TestCase, Config) ->
end_per_testcase(_TestCase, Config) ->
Config.
t_load(_) ->
error('TODO').
% t_load(_) ->
% error('TODO').
t_unload(_) ->
error('TODO').
% t_unload(_) ->
% error('TODO').
t_on_client_connected(_) ->
error('TODO').
% t_on_client_connected(_) ->
% error('TODO').
t_on_client_disconnected(_) ->
error('TODO').
% t_on_client_disconnected(_) ->
% error('TODO').

View File

@ -29,18 +29,18 @@ init_per_testcase(_TestCase, Config) ->
end_per_testcase(_TestCase, Config) ->
Config.
t_load(_) ->
error('TODO').
% t_load(_) ->
% error('TODO').
t_rewrite_subscribe(_) ->
error('TODO').
% t_rewrite_subscribe(_) ->
% error('TODO').
t_rewrite_unsubscribe(_) ->
error('TODO').
% t_rewrite_unsubscribe(_) ->
% error('TODO').
t_rewrite_publish(_) ->
error('TODO').
% t_rewrite_publish(_) ->
% error('TODO').
t_unload(_) ->
error('TODO').
% t_unload(_) ->
% error('TODO').

View File

@ -29,12 +29,12 @@ init_per_testcase(_TestCase, Config) ->
end_per_testcase(_TestCase, Config) ->
Config.
t_load(_) ->
error('TODO').
% t_load(_) ->
% error('TODO').
t_on_client_connected(_) ->
error('TODO').
% t_on_client_connected(_) ->
% error('TODO').
t_unload(_) ->
error('TODO').
% t_unload(_) ->
% error('TODO').

View File

@ -52,12 +52,11 @@ end_per_suite(_Config) ->
%% Test cases
%%--------------------------------------------------------------------
t_unload(_) ->
error('TODO').
t_load(_) ->
error('TODO').
% t_unload(_) ->
% error('TODO').
% t_load(_) ->
% error('TODO').
%% Test case for emqx_mod_presence
t_mod_presence(_) ->

View File

@ -24,13 +24,11 @@
all() -> emqx_ct:all(?MODULE).
% t_get_caps(_) ->
% error('TODO').
t_get_caps(_) ->
error('TODO').
t_default(_) ->
error('TODO').
% t_default(_) ->
% error('TODO').
t_check_pub(_) ->
PubCaps = #{max_qos_allowed => ?QOS_1,

View File

@ -79,11 +79,11 @@ foreach_prop(Fun) ->
lists:foreach(Fun, maps:to_list(emqx_mqtt_props:all())).
t_all(_) ->
error('TODO').
% t_all(_) ->
% error('TODO').
t_set(_) ->
error('TODO').
% t_set(_) ->
% error('TODO').
t_get(_) ->
error('TODO').
% t_get(_) ->
% error('TODO').

View File

@ -29,23 +29,23 @@
all() -> emqx_ct:all(?MODULE).
t_init(_) ->
error('TODO').
% t_init(_) ->
% error('TODO').
t_is_empty(_) ->
error('TODO').
% t_is_empty(_) ->
% error('TODO').
t_len(_) ->
error('TODO').
% t_len(_) ->
% error('TODO').
t_max_len(_) ->
error('TODO').
% t_max_len(_) ->
% error('TODO').
t_dropped(_) ->
error('TODO').
% t_dropped(_) ->
% error('TODO').
t_stats(_) ->
error('TODO').
% t_stats(_) ->
% error('TODO').
t_in(_) ->
Opts = #{max_len => 5, store_qos0 => true},

View File

@ -1,48 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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_oom_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
all() -> emqx_ct:all(?MODULE).
t_init(_) ->
Opts = #{message_queue_len => 10,
max_heap_size => 1024*1024*8
},
Oom = emqx_oom:init(Opts),
?assertEqual(#{message_queue_len => 10,
max_heap_size => 1024*1024
}, emqx_oom:info(Oom)).
t_check(_) ->
Opts = #{message_queue_len => 10,
max_heap_size => 1024*1024*8
},
Oom = emqx_oom:init(Opts),
[self() ! {msg, I} || I <- lists:seq(1, 5)],
?assertEqual(ok, emqx_oom:check(Oom)),
[self() ! {msg, I} || I <- lists:seq(1, 6)],
?assertEqual({shutdown, message_queue_too_long}, emqx_oom:check(Oom)).
t_info(_) ->
error('TODO').

View File

@ -29,43 +29,15 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
application:stop(os_mon).
t_get_cpu_check_interval(_) ->
error('TODO').
t_set_cpu_check_interval(_) ->
error('TODO').
% t_set_mem_check_interval(_) ->
% error('TODO').
t_get_cpu_high_watermark(_) ->
error('TODO').
% t_set_sysmem_high_watermark(_) ->
% error('TODO').
t_set_cpu_high_watermark(_) ->
error('TODO').
t_get_cpu_low_watermark(_) ->
error('TODO').
t_set_cpu_low_watermark(_) ->
error('TODO').
t_get_mem_check_interval(_) ->
error('TODO').
t_set_mem_check_interval(_) ->
error('TODO').
t_get_sysmem_high_watermark(_) ->
error('TODO').
t_set_sysmem_high_watermark(_) ->
error('TODO').
t_get_procmem_high_watermark(_) ->
error('TODO').
t_set_procmem_high_watermark(_) ->
error('TODO').
% t_set_procmem_high_watermark(_) ->
% error('TODO').
t_api(_) ->
gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}),
@ -84,11 +56,44 @@ t_api(_) ->
% timer:sleep(2000),
% ?assertEqual(true, lists:keymember(cpu_high_watermark, 1, alarm_handler:get_alarms())),
emqx_os_mon:set_cpu_check_interval(0.05),
emqx_os_mon:set_cpu_high_watermark(0.8),
emqx_os_mon:set_cpu_low_watermark(0.75),
?assertEqual(0.05, emqx_os_mon:get_cpu_check_interval()),
?assertEqual(0.8, emqx_os_mon:get_cpu_high_watermark()),
?assertEqual(0.75, emqx_os_mon:get_cpu_low_watermark()),
% timer:sleep(3000),
% ?assertEqual(false, lists:keymember(cpu_high_watermark, 1, alarm_handler:get_alarms())),
?assertEqual(ignored, gen_server:call(emqx_os_mon, ignored)),
?assertEqual(ok, gen_server:cast(emqx_os_mon, ignored)),
emqx_os_mon ! ignored,
gen_server:stop(emqx_os_mon),
ok.
t_timeout(_) ->
ok = meck:new(emqx_vm),
ok = meck:expect(emqx_vm, cpu_util, fun() -> 0 end),
{ok, _} = emqx_os_mon:start_link([{cpu_check_interval, 1}]),
timer:sleep(1500),
gen_server:stop(emqx_os_mon),
ok = meck:expect(emqx_vm, cpu_util, fun() -> {error, test_case} end),
{ok, _} = emqx_os_mon:start_link([{cpu_check_interval, 1}]),
timer:sleep(1500),
gen_server:stop(emqx_os_mon),
ok = meck:expect(emqx_vm, cpu_util, fun() -> 90 end),
{ok, _} = emqx_os_mon:start_link([{cpu_check_interval, 1},
{cpu_high_watermark, 0.80},
{cpu_low_watermark, 0.60}]),
timer:sleep(1500),
emqx_os_mon:set_cpu_high_watermark(1.00),
timer:sleep(1500),
emqx_os_mon:set_cpu_low_watermark(0.95),
timer:sleep(1500),
gen_server:stop(emqx_os_mon),
ok = meck:unload(emqx_vm).

View File

@ -82,17 +82,20 @@ t_check_publish(_) ->
Props = #{'Response-Topic' => <<"responsetopic">>, 'Topic-Alias' => 1},
ok = emqx_packet:check(?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, Props, <<"payload">>)),
ok = emqx_packet:check(#mqtt_packet_publish{packet_id = 1, topic_name = <<"t">>}),
ok = emqx_packet:check(#mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias'=> 0}}),
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(?QOS_1, <<>>, 1, #{}, <<"payload">>)),
{error, ?RC_TOPIC_NAME_INVALID} = emqx_packet:check(?PUBLISH_PACKET(?QOS_1, <<"+/+">>, 1, #{}, <<"payload">>)),
{error, ?RC_TOPIC_ALIAS_INVALID} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Topic-Alias' => 0}, <<"payload">>)),
%% TODO::
%% {error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Subscription-Identifier' => 10}, <<"payload">>)),
ok = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Subscription-Identifier' => 10}, <<"payload">>)),
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Subscription-Identifier' => 0}, <<"payload">>)),
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Response-Topic' => <<"+/+">>}, <<"payload">>)).
t_check_subscribe(_) ->
ok = emqx_packet:check(?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => 1},
[{<<"topic">>, #{qos => ?QOS_0}}])),
{error, ?RC_TOPIC_FILTER_INVALID} = emqx_packet:check(#mqtt_packet_subscribe{topic_filters = []}),
{error, ?RC_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED} =
emqx_packet:check(?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => -1},
[{<<"topic">>, #{qos => ?QOS_0, rp => 0}}])).
@ -104,7 +107,11 @@ t_check_unsubscribe(_) ->
t_check_connect(_) ->
Opts = #{max_clientid_len => 5, mqtt_retain_available => false},
ok = emqx_packet:check(#mqtt_packet_connect{}, Opts),
ok = emqx_packet:check(?CONNECT_PACKET(#mqtt_packet_connect{properties = #{'Receive-Maximum' => 1}}), Opts),
ok = emqx_packet:check(?CONNECT_PACKET(#mqtt_packet_connect{clientid = <<1>>,
properties = #{'Receive-Maximum' => 1},
will_flag = true,
will_topic = <<"will_topic">>}
), Opts),
ConnPkt1 = #mqtt_packet_connect{proto_name = <<"MQIsdp">>,
proto_ver = ?MQTT_PROTO_V5
},
@ -137,7 +144,9 @@ t_check_connect(_) ->
properties = #{'Request-Problem-Information' => 2}}), Opts),
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(
?CONNECT_PACKET(#mqtt_packet_connect{
properties = #{'Receive-Maximum' => 0}}), Opts).
properties = #{'Receive-Maximum' => 0}}), Opts),
ConnPkt7 = #mqtt_packet_connect{clientid = <<>>, clean_start = false},
{error, ?RC_CLIENT_IDENTIFIER_NOT_VALID} = emqx_packet:check(ConnPkt7, Opts).
t_from_to_message(_) ->
ExpectedMsg = emqx_message:make(<<"clientid">>, ?QOS_0, <<"topic">>, <<"payload">>),
@ -160,6 +169,7 @@ t_from_to_message(_) ->
}).
t_will_msg(_) ->
?assertEqual(undefined, emqx_packet:will_msg(#mqtt_packet_connect{will_flag = false})),
Pkt = #mqtt_packet_connect{will_flag = true,
clientid = <<"clientid">>,
username = "test",
@ -171,14 +181,30 @@ t_will_msg(_) ->
},
Msg = emqx_packet:will_msg(Pkt),
?assertEqual(<<"clientid">>, Msg#message.from),
?assertEqual(<<"topic">>, Msg#message.topic).
?assertEqual(<<"topic">>, Msg#message.topic),
Pkt2 = #mqtt_packet_connect{will_flag = true,
clientid = <<"clientid">>,
username = "test",
will_retain = true,
will_qos = ?QOS_2,
will_topic = <<"topic">>,
will_props = undefined,
will_payload = <<"payload">>
},
Msg2 = emqx_packet:will_msg(Pkt2),
?assertEqual(<<"clientid">>, Msg2#message.from),
?assertEqual(<<"topic">>, Msg2#message.topic).
t_to_message(_) ->
error('TODO').
t_format(_) ->
io:format("~s", [emqx_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{}))]),
io:format("~s", [emqx_packet:format(#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK, retain = true, dup = 0}, variable = undefined})]),
io:format("~s", [emqx_packet:format(#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK}, variable = 1, payload = <<"payload">>})]),
io:format("~s", [emqx_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{will_flag = true,
will_retain = true,
will_qos = ?QOS_2,
will_topic = <<"topic">>,
will_props = undefined,
will_payload = <<"payload">>}))]),
io:format("~s", [emqx_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{password = password}))]),
io:format("~s", [emqx_packet:format(?CONNACK_PACKET(?CONNACK_SERVER))]),
io:format("~s", [emqx_packet:format(?PUBLISH_PACKET(?QOS_1, 1))]),
io:format("~s", [emqx_packet:format(?PUBLISH_PACKET(?QOS_2, <<"topic">>, 10, <<"payload">>))]),
@ -187,5 +213,6 @@ t_format(_) ->
io:format("~s", [emqx_packet:format(?SUBSCRIBE_PACKET(15, [{<<"topic">>, ?QOS_0}, {<<"topic1">>, ?QOS_1}]))]),
io:format("~s", [emqx_packet:format(?SUBACK_PACKET(40, [?QOS_0, ?QOS_1]))]),
io:format("~s", [emqx_packet:format(?UNSUBSCRIBE_PACKET(89, [<<"t">>, <<"t2">>]))]),
io:format("~s", [emqx_packet:format(?UNSUBACK_PACKET(90))]).
io:format("~s", [emqx_packet:format(?UNSUBACK_PACKET(90))]),
io:format("~s", [emqx_packet:format(?DISCONNECT_PACKET(128))]).

View File

@ -23,16 +23,10 @@
all() -> emqx_ct:all(?MODULE).
t_get_counter(_) ->
error('TODO').
t_reset_counter(_) ->
error('TODO').
t_update_counter(_) ->
?assertEqual(undefined, emqx_pd:update_counter(bytes, 1)),
?assertEqual(1, emqx_pd:update_counter(bytes, 1)),
?assertEqual(2, emqx_pd:update_counter(bytes, 1)),
?assertEqual(undefined, emqx_pd:inc_counter(bytes, 1)),
?assertEqual(1, emqx_pd:inc_counter(bytes, 1)),
?assertEqual(2, emqx_pd:inc_counter(bytes, 1)),
?assertEqual(3, emqx_pd:get_counter(bytes)),
?assertEqual(3, emqx_pd:reset_counter(bytes)),
?assertEqual(0, emqx_pd:get_counter(bytes)).

View File

@ -43,22 +43,20 @@ init_per_suite(Config) ->
Config.
t_load_expand_plugin(_) ->
error('TODO').
% t_load_expand_plugin(_) ->
% error('TODO').
t_list(_) ->
error('TODO').
% t_list(_) ->
% error('TODO').
t_find_plugin(_) ->
error('TODO').
% t_find_plugin(_) ->
% error('TODO').
t_unload(_) ->
error('TODO').
% t_unload(_) ->
% error('TODO').
t_init(_) ->
error('TODO').
% t_init(_) ->
% error('TODO').
set_sepecial_cfg(_) ->
ExpandPath = filename:dirname(code:lib_dir(emqx_mini_plugin)),

View File

@ -23,11 +23,11 @@
all() -> emqx_ct:all(?MODULE).
t_new(_) ->
error('TODO').
% t_new(_) ->
% error('TODO').
t_count(_) ->
error('TODO').
% t_count(_) ->
% error('TODO').
t_monitor(_) ->
PMon = emqx_pmon:new(),
@ -37,8 +37,8 @@ t_monitor(_) ->
PMon2 = emqx_pmon:demonitor(self(), PMon2),
?assertEqual(0, emqx_pmon:count(PMon2)).
t_demonitor(_) ->
error('TODO').
% t_demonitor(_) ->
% error('TODO').
t_find(_) ->
PMon = emqx_pmon:new(),
@ -60,5 +60,5 @@ t_erase(_) ->
?assertEqual([{self(), val}], Items),
?assertEqual(0, emqx_pmon:count(PMon3)).
t_erase_all(_) ->
error('TODO').
% t_erase_all(_) ->
% error('TODO').

View File

@ -86,6 +86,5 @@ t_unexpected(_) ->
test_mfa() ->
lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]).
t_async_submit(_) ->
error('TODO').
% t_async_submit(_) ->
% error('TODO').

View File

@ -26,142 +26,152 @@
all() -> emqx_ct:all(?SUITE).
t_is_queue(_) ->
error('TODO').
Q = ?PQ:new(),
?assertEqual(true, ?PQ:is_queue(Q)),
Q1 = ?PQ:in(a, 1, Q),
?assertEqual(true, ?PQ:is_queue(Q1)),
?assertEqual(false, ?PQ:is_queue(bad_queue)).
t_is_empty(_) ->
error('TODO').
t_to_list(_) ->
error('TODO').
t_from_list(_) ->
error('TODO').
t_in(_) ->
error('TODO').
t_out_p(_) ->
error('TODO').
t_join(_) ->
error('TODO').
t_filter(_) ->
error('TODO').
t_fold(_) ->
error('TODO').
t_highest(_) ->
error('TODO').
t_out(_) ->
error('TODO').
Q = ?PQ:new(),
?assertEqual(true, ?PQ:is_empty(Q)),
?assertEqual(false, ?PQ:is_empty(?PQ:in(a, Q))).
t_len(_) ->
error('TODO').
Q = ?PQ:new(),
Q1 = ?PQ:in(a, Q),
?assertEqual(1, ?PQ:len(Q1)),
Q2 = ?PQ:in(b, 1, Q1),
?assertEqual(2, ?PQ:len(Q2)).
t_plen(_) ->
error('TODO').
t_new(_) ->
error('TODO').
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).
Q1 = ?PQ:in(a, Q),
?assertEqual(1, ?PQ:plen(0, Q1)),
?assertEqual(0, ?PQ:plen(1, Q1)),
Q2 = ?PQ:in(b, 1, Q1),
Q3 = ?PQ:in(c, 1, Q2),
?assertEqual(2, ?PQ:plen(1, Q3)),
?assertEqual(1, ?PQ:plen(0, Q3)),
?assertEqual(0, ?PQ:plen(0, {pqueue, []})).
t_priority_queue_out2(_) ->
Els = [a, {b, 1}, {c, 1}, {d, 2}, {e, 2}, {f, 2}],
Q = ?PQ:new(),
Q0 = lists:foldl(
t_to_list(_) ->
Q = ?PQ:new(),
?assertEqual([], ?PQ:to_list(Q)),
Q1 = ?PQ:in(a, Q),
L1 = ?PQ:to_list(Q1),
?assertEqual([{0, a}], L1),
Q2 = ?PQ:in(b, 1, Q1),
L2 = ?PQ:to_list(Q2),
?assertEqual([{1, b}, {0, a}], L2).
t_from_list(_) ->
Q = ?PQ:from_list([{1, c}, {1, d}, {0, a}, {0, b}]),
?assertEqual({pqueue, [{-1, {queue, [d], [c], 2}}, {0, {queue, [b], [a], 2}}]}, Q),
?assertEqual(true, ?PQ:is_queue(Q)),
?assertEqual(4, ?PQ:len(Q)).
t_in(_) ->
Q = ?PQ:new(),
Els = [a, b, {c, 1}, {d, 1}, {e, infinity}, {f, 2}],
Q1 = 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).
?assertEqual({pqueue, [{infinity, {queue, [e], [], 1}},
{-2, {queue, [f], [], 1}},
{-1, {queue, [d], [c], 2}},
{0, {queue, [b], [a], 2}}]}, Q1).
t_priority_queues(_) ->
Q0 = ?PQ:new(),
Q1 = ?PQ:new(),
PQueue = {pqueue, [{0, Q0}, {1, Q1}]},
?assert(?PQ:is_queue(PQueue)),
[] = ?PQ:to_list(PQueue),
t_out(_) ->
Q = ?PQ:new(),
{empty, Q} = ?PQ:out(Q),
{empty, Q} = ?PQ:out(0, Q),
try ?PQ:out(1, Q) of
_ -> ct:fail(should_throw_error)
catch error:Reason ->
?assertEqual(Reason, badarg)
end,
{{value, a}, Q} = ?PQ:out(?PQ:from_list([{0, a}])),
{{value, a}, {queue, [], [b], 1}} = ?PQ:out(?PQ:from_list([{0, a}, {0, b}])),
{{value, a}, {queue, [], [], 0}} = ?PQ:out({queue, [], [a], 1}),
{{value, a}, {queue, [c], [b], 2}} = ?PQ:out({queue, [c, b], [a], 3}),
{{value, a}, {queue, [e, d], [b, c], 4}} = ?PQ:out({queue, [e, d, c, b], [a], 5}),
{{value, a}, {queue, [c], [b], 2}} = ?PQ:out({queue, [c, b, a], [], 3}),
{{value, a}, {queue, [d, c], [b], 3}} = ?PQ:out({queue, [d, c], [a, b], 4}),
{{value, a}, {queue, [], [], 0}} = ?PQ:out(?PQ:from_list([{1, a}])),
{{value, a}, {queue, [c], [b], 2}} = ?PQ:out(?PQ:from_list([{1, a}, {0, b}, {0, c}])),
{{value, a}, {pqueue, [{-1, {queue, [b], [], 1}}]}} = ?PQ:out(?PQ:from_list([{1, b}, {2, a}])),
{{value, a}, {pqueue, [{-1, {queue, [], [b], 1}}]}} = ?PQ:out(?PQ:from_list([{1, a}, {1, b}])).
PQueue1 = ?PQ:in(a, 0, ?PQ:new()),
PQueue2 = ?PQ:in(b, 0, PQueue1),
t_out_2(_) ->
{empty, {pqueue, [{-1, {queue, [a], [], 1}}]}} = ?PQ:out(0, ?PQ:from_list([{1, a}])),
{{value, a}, {queue, [], [], 0}} = ?PQ:out(1, ?PQ:from_list([{1, a}])),
{{value, a}, {pqueue, [{-1, {queue, [], [b], 1}}]}} = ?PQ:out(1, ?PQ:from_list([{1, a}, {1, b}])),
{{value, a}, {queue, [b], [], 1}} = ?PQ:out(1, ?PQ:from_list([{1, a}, {0, b}])).
PQueue3 = ?PQ:in(c, 1, PQueue2),
PQueue4 = ?PQ:in(d, 1, PQueue3),
t_out_p(_) ->
{empty, {queue, [], [], 0}} = ?PQ:out_p(?PQ:new()),
{{value, a, 1}, {queue, [b], [], 1}} = ?PQ:out_p(?PQ:from_list([{1, a}, {0, b}])).
4 = ?PQ:len(PQueue4),
t_join(_) ->
Q = ?PQ:in(a, ?PQ:new()),
Q = ?PQ:join(Q, ?PQ:new()),
Q = ?PQ:join(?PQ:new(), Q),
[{1, c}, {1, d}, {0, a}, {0, b}] = ?PQ:to_list(PQueue4),
PQueue4 = ?PQ:from_list([{1, c}, {1, d}, {0, a}, {0, b}]),
Q1 = ?PQ:in(a, ?PQ:new()),
Q2 = ?PQ:in(b, Q1),
Q3 = ?PQ:in(c, Q2),
{queue,[c,b],[a],3} = Q3,
Q4 = ?PQ:in(x, ?PQ:new()),
Q5 = ?PQ:in(y, Q4),
Q6 = ?PQ:in(z, Q5),
{queue,[z,y],[x],3} = Q6,
{queue,[z,y],[a,b,c,x],6} = ?PQ:join(Q3, Q6),
PQueue1 = ?PQ:from_list([{1, c}, {1, d}]),
PQueue2 = ?PQ:from_list([{1, c}, {1, d}, {0, a}, {0, b}]),
PQueue3 = ?PQ:from_list([{1, c}, {1, d}, {-1, a}, {-1, b}]),
{pqueue,[{-1,{queue,[d],[c],2}},
{0,{queue,[z,y],[x],3}}]} = ?PQ:join(PQueue1, Q6),
{pqueue,[{-1,{queue,[d],[c],2}},
{0,{queue,[z,y],[x],3}}]} = ?PQ:join(Q6, PQueue1),
{pqueue,[{-1,{queue,[d],[c],2}},
{0,{queue,[z,y],[a,b,x],5}}]} = ?PQ:join(PQueue2, Q6),
{pqueue,[{-1,{queue,[d],[c],2}},
{0,{queue,[b],[x,y,z,a],5}}]} = ?PQ:join(Q6, PQueue2),
{pqueue,[{-1,{queue,[d],[c],2}},
{0,{queue,[z,y],[x],3}},
{1,{queue,[b],[a],2}}]} = ?PQ:join(PQueue3, Q6),
{pqueue,[{-1,{queue,[d],[c],2}},
{0,{queue,[z,y],[x],3}},
{1,{queue,[b],[a],2}}]} = ?PQ:join(Q6, PQueue3),
PQueue4 = ?PQ:from_list([{1, c}, {1, d}]),
PQueue5 = ?PQ:from_list([{2, a}, {2, b}]),
{pqueue,[{-2,{queue,[b],[a],2}},
{-1,{queue,[d],[c],2}}]} = ?PQ:join(PQueue4, PQueue5).
t_filter(_) ->
{pqueue, [{-2, {queue, [10], [4], 2}},
{-1, {queue, [2], [], 1}}]} =
?PQ:filter(fun(V) when V rem 2 =:= 0 ->
true;
(_) ->
false
end, ?PQ:from_list([{0, 1}, {0, 3}, {1, 2}, {2, 4}, {2, 10}])).
t_highest(_) ->
empty = ?PQ:highest(?PQ:new()),
0 = ?PQ:highest(PQueue1),
1 = ?PQ:highest(PQueue4),
PQueue5 = ?PQ:in(e, infinity, PQueue4),
PQueue6 = ?PQ:in(f, 1, PQueue5),
{{value, e}, PQueue7} = ?PQ:out(PQueue6),
{empty, _} = ?PQ:out(0, ?PQ:new()),
{empty, Q0} = ?PQ:out_p(Q0),
Q2 = ?PQ:in(a, Q0),
Q3 = ?PQ:in(b, Q2),
Q4 = ?PQ:in(c, Q3),
{{value, a, 0}, _Q5} = ?PQ:out_p(Q4),
{{value,c,1}, PQueue8} = ?PQ:out_p(PQueue7),
Q4 = ?PQ:join(Q4, ?PQ:new()),
Q4 = ?PQ:join(?PQ:new(), Q4),
{queue, [a], [a], 2} = ?PQ:join(Q2, Q2),
{pqueue,[{-1,{queue,[f],[d],2}},
{0,{queue,[a],[a,b],3}}]} = ?PQ:join(PQueue8, Q2),
{pqueue,[{-1,{queue,[f],[d],2}},
{0,{queue,[b],[a,a],3}}]} = ?PQ:join(Q2, PQueue8),
{pqueue,[{-1,{queue,[f],[d,f,d],4}},
{0,{queue,[b],[a,b,a],4}}]} = ?PQ:join(PQueue8, PQueue8).
0 = ?PQ:highest(?PQ:from_list([{0, a}, {0, b}])),
2 = ?PQ:highest(?PQ:from_list([{0, a}, {0, b}, {1, c}, {2, d}, {2, e}])).

View File

@ -25,24 +25,23 @@
all() -> emqx_ct:all(?MODULE).
% t_name(_) ->
% error('TODO').
t_name(_) ->
error('TODO').
% t_text(_) ->
% error('TODO').
t_text(_) ->
error('TODO').
% t_mqtt_frame_error(_) ->
% error('TODO').
t_mqtt_frame_error(_) ->
error('TODO').
% t_connack_error(_) ->
% error('TODO').
t_connack_error(_) ->
error('TODO').
% t_compat(_) ->
% error('TODO').
t_compat(_) ->
error('TODO').
t_formalized(_) ->
error('TODO').
% t_formalized(_) ->
% error('TODO').
t_prop_name_text(_) ->
?assert(proper:quickcheck(prop_name_text(), prop_name_text(opts))).

View File

@ -45,23 +45,23 @@ t_mnesia(_) ->
%% for coverage
ok = emqx_router:mnesia(copy).
t_add_route(_) ->
error('TODO').
% t_add_route(_) ->
% error('TODO').
t_do_add_route(_) ->
error('TODO').
% t_do_add_route(_) ->
% error('TODO').
t_lookup_routes(_) ->
error('TODO').
% t_lookup_routes(_) ->
% error('TODO').
t_delete_route(_) ->
error('TODO').
% t_delete_route(_) ->
% error('TODO').
t_do_delete_route(_) ->
error('TODO').
% t_do_delete_route(_) ->
% error('TODO').
t_topics(_) ->
error('TODO').
% t_topics(_) ->
% error('TODO').
t_add_delete(_) ->
?R:add_route(<<"a/b/c">>),

View File

@ -29,13 +29,12 @@ init_per_testcase(_TestCase, Config) ->
end_per_testcase(_TestCase, Config) ->
Config.
t_mnesia(_) ->
error('TODO').
% t_mnesia(_) ->
% error('TODO').
% t_monitor(_) ->
% error('TODO').
t_monitor(_) ->
error('TODO').
t_stats_fun(_) ->
error('TODO').
% t_stats_fun(_) ->
% error('TODO').

View File

@ -24,17 +24,6 @@
all() -> emqx_ct:all(?MODULE).
t_multicall(_) ->
error('TODO').
t_cast(_) ->
error('TODO').
t_call(_) ->
error('TODO').
t_prop_rpc(_) ->
ok = load(),
Opts = [{to_file, user}, {numtests, 10}],
@ -42,7 +31,9 @@ t_prop_rpc(_) ->
ok = application:set_env(gen_rpc, call_receive_timeout, 1),
ok = emqx_logger:set_log_level(emergency),
?assert(proper:quickcheck(prop_node(), Opts)),
?assert(proper:quickcheck(prop_node_with_key(), Opts)),
?assert(proper:quickcheck(prop_nodes(), Opts)),
?assert(proper:quickcheck(prop_nodes_with_key(), Opts)),
ok = application:stop(gen_rpc),
ok = unload().
@ -57,6 +48,17 @@ prop_node() ->
end
end).
prop_node_with_key() ->
?FORALL({Node, Key}, nodename_with_key(),
begin
?assert(emqx_rpc:cast(Key, Node, erlang, system_time, [])),
case emqx_rpc:call(Key, Node, erlang, system_time, []) of
{badrpc, _Reason} -> true;
Delivery when is_integer(Delivery) -> true;
_Other -> false
end
end).
prop_nodes() ->
?FORALL(Nodes, nodesname(),
begin
@ -70,6 +72,19 @@ prop_nodes() ->
end
end).
prop_nodes_with_key() ->
?FORALL({Nodes, Key}, nodesname_with_key(),
begin
case emqx_rpc:multicall(Key, Nodes, erlang, system_time, []) of
{badrpc, _Reason} -> true;
{RealResults, RealBadNodes}
when is_list(RealResults);
is_list(RealBadNodes) ->
true;
_Other -> false
end
end).
%%--------------------------------------------------------------------
%% helper
%%--------------------------------------------------------------------
@ -96,8 +111,19 @@ nodename() ->
list_to_atom(Node)
end).
nodename_with_key() ->
?LET({NodePrefix, HostName, Key},
{node_prefix(), hostname(), choose(0, 10)},
begin
Node = NodePrefix ++ "@" ++ HostName,
{list_to_atom(Node), Key}
end).
nodesname() ->
oneof([list(nodename()), ["emqxct@127.0.0.1"]]).
oneof([list(nodename()), ['emqxct@127.0.0.1']]).
nodesname_with_key() ->
oneof([{list(nodename()), choose(0, 10)}, {['emqxct@127.0.0.1'], 1}]).
node_prefix() ->
oneof(["emqxct", text_like()]).

View File

@ -29,22 +29,20 @@
all() -> emqx_ct:all(?MODULE).
% t_currval(_) ->
% error('TODO').
t_currval(_) ->
error('TODO').
% t_delete(_) ->
% error('TODO').
t_delete(_) ->
error('TODO').
% t_create(_) ->
% error('TODO').
t_create(_) ->
error('TODO').
t_reclaim(_) ->
error('TODO').
t_nextval(_) ->
error('TODO').
% t_reclaim(_) ->
% error('TODO').
% t_nextval(_) ->
% error('TODO').
t_generate(_) ->
ok = emqx_sequence:create(seqtab),

View File

@ -39,20 +39,20 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
t_is_ack_required(_) ->
error('TODO').
% t_is_ack_required(_) ->
% error('TODO').
t_maybe_nack_dropped(_) ->
error('TODO').
% t_maybe_nack_dropped(_) ->
% error('TODO').
t_nack_no_connection(_) ->
error('TODO').
% t_nack_no_connection(_) ->
% error('TODO').
t_maybe_ack(_) ->
error('TODO').
% t_maybe_ack(_) ->
% error('TODO').
t_subscribers(_) ->
error('TODO').
% t_subscribers(_) ->
% error('TODO').
t_random_basic(_) ->
ok = ensure_config(random),
@ -239,15 +239,14 @@ last_message(ExpectedPayload, Pids) ->
<<"not yet?">>
end.
t_dispatch(_) ->
error('TODO').
% t_dispatch(_) ->
% error('TODO').
t_unsubscribe(_) ->
error('TODO').
t_subscribe(_) ->
error('TODO').
% t_unsubscribe(_) ->
% error('TODO').
% t_subscribe(_) ->
% error('TODO').
%%--------------------------------------------------------------------
%% help functions

View File

@ -23,21 +23,20 @@
all() -> emqx_ct:all(?MODULE).
t_cast_useless_msg(_)->
emqx_stats:setstat('notExis', 1),
with_proc(fun() ->
emqx_stats ! useless,
?assertEqual(ok, gen_server:cast(emqx_stats, useless))
end).
t_statsfun(_) ->
error('TODO').
t_getstats(_) ->
error('TODO').
t_getstat(_) ->
error('TODO').
t_setstat(_) ->
error('TODO').
t_get_error_state(_) ->
Conns = emqx_stats:getstats(),
?assertEqual([], Conns).
t_get_state(_) ->
with_proc(fun() ->
?assertEqual(undefined, emqx_stats:getstat('notExist')),
SetConnsCount = emqx_stats:statsfun('connections.count'),
SetConnsCount(1),
?assertEqual(1, emqx_stats:getstat('connections.count')),
@ -69,6 +68,8 @@ t_update_interval(_) ->
UpdFun = fun() -> emqx_stats:setstat('connections.count', 1) end,
ok = emqx_stats:update_interval(stats_test, UpdFun),
timer:sleep(SleepMs),
ok = emqx_stats:update_interval(stats_test, UpdFun),
timer:sleep(SleepMs),
?assertEqual(1, emqx_stats:getstat('connections.count'))
end, TickMs).

View File

@ -43,27 +43,26 @@ end_per_suite(_Config) ->
ok = emqx_logger:set_log_level(error),
ok.
t_version(_) ->
error('TODO').
% t_version(_) ->
% error('TODO').
t_sysdescr(_) ->
error('TODO').
% t_sysdescr(_) ->
% error('TODO').
t_uptime(_) ->
error('TODO').
% t_uptime(_) ->
% error('TODO').
t_datetime(_) ->
error('TODO').
% t_datetime(_) ->
% error('TODO').
t_sys_interval(_) ->
error('TODO').
% t_sys_interval(_) ->
% error('TODO').
t_sys_heatbeat_interval(_) ->
error('TODO').
t_info(_) ->
error('TODO').
% t_sys_heatbeat_interval(_) ->
% error('TODO').
% t_info(_) ->
% error('TODO').
t_prop_sys(_) ->
Opts = [{numtests, 100}, {to_file, user}],

View File

@ -28,6 +28,8 @@
concat_str("long_gc warning: pid = ~p, info: ~p", self(), "hello"), "hello"},
{self(), long_schedule,
concat_str("long_schedule warning: pid = ~p, info: ~p", self(), "hello"), "hello"},
{self(), large_heap,
concat_str("large_heap warning: pid = ~p, info: ~p", self(), "hello"), "hello"},
{self(), busy_port,
concat_str("busy_port warning: suspid = ~p, port = ~p",
self(), list_to_port("#Port<0.4>")), list_to_port("#Port<0.4>")},
@ -41,12 +43,35 @@
all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) ->
init_per_testcase(t_sys_mon, Config) ->
emqx_ct_helpers:boot_modules(all),
emqx_ct_helpers:start_apps([]),
emqx_ct_helpers:start_apps([],
fun(emqx) ->
application:set_env(emqx, sysmon, [{busy_dist_port,true},
{busy_port,false},
{large_heap,8388608},
{long_schedule,240},
{long_gc,0}]),
ok;
(_) -> ok
end),
Config;
init_per_testcase(t_sys_mon2, Config) ->
emqx_ct_helpers:boot_modules(all),
emqx_ct_helpers:start_apps([],
fun(emqx) ->
application:set_env(emqx, sysmon, [{busy_dist_port,false},
{busy_port,true},
{large_heap,8388608},
{long_schedule,0},
{long_gc,200},
{nothing, 0}]),
ok;
(_) -> ok
end),
Config.
end_per_suite(_Config) ->
end_per_testcase(_, _Config) ->
emqx_ct_helpers:stop_apps([]).
t_sys_mon(_Config) ->
@ -55,6 +80,13 @@ t_sys_mon(_Config) ->
validate_sys_mon_info(PidOrPort, SysMonName,ValidateInfo, InfoOrPort)
end, ?INPUTINFO).
t_sys_mon2(_Config) ->
?SYSMON ! {timeout, ignored, reset},
?SYSMON ! {ignored},
?assertEqual(ignored, gen_server:call(?SYSMON, ignored)),
?assertEqual(ok, gen_server:cast(?SYSMON, ignored)),
gen_server:stop(?SYSMON).
validate_sys_mon_info(PidOrPort, SysMonName,ValidateInfo, InfoOrPort) ->
{ok, C} = emqtt:start_link([{host, "localhost"}]),
{ok, _} = emqtt:connect(C),

View File

@ -30,5 +30,4 @@ t_now_secs(_) ->
?assert(emqx_time:now_secs() =< emqx_time:now_secs(os:timestamp())).
t_now_ms(_) ->
?assert(emqx_time:now_ms() =< emqx_time:now_ms(os:timestamp())).
?assert(emqx_time:now_ms() =< emqx_time:now_ms(os:timestamp())).

View File

@ -229,7 +229,3 @@ bench(Case, Fun, Args) ->
]),
ct:pal("Time consumed by ~s: ~.3f(us)~nCall ~s per second: ~w",
[Case, Time/?N, Case, (?N * 1000000) div Time]).
t_match(_) ->
error('TODO').

View File

@ -47,9 +47,12 @@ t_start_traces(_Config) ->
{error, _} = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"),
emqx_logger:set_log_level(debug),
ok = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"),
ok = emqx_tracer:start_trace({clientid, <<"client2">>}, all, "tmp/client2.log"),
{error, {invalid_log_level, bad_level}} = emqx_tracer:start_trace({clientid, <<"client3">>}, bad_level, "tmp/client3.log"),
ok = emqx_tracer:start_trace({clientid, "client2"}, all, "tmp/client2.log"),
ok = emqx_tracer:start_trace({clientid, client3}, all, "tmp/client3.log"),
{error, {invalid_log_level, bad_level}} = emqx_tracer:start_trace({clientid, <<"client4">>}, bad_level, "tmp/client4.log"),
{error, {handler_not_added, {file_error,".",eisdir}}} = emqx_tracer:start_trace({clientid, <<"client5">>}, debug, "."),
ok = emqx_tracer:start_trace({topic, <<"a/#">>}, all, "tmp/topic_trace.log"),
ok = emqx_tracer:start_trace({topic, <<"b/#">>}, all, "tmp/topic_trace.log"),
ct:sleep(100),
%% Verify the tracing file exits
@ -60,7 +63,9 @@ t_start_traces(_Config) ->
%% Get current traces
?assertEqual([{{clientid,"client"},{debug,"tmp/client.log"}},
{{clientid,"client2"},{debug,"tmp/client2.log"}},
{{topic,"a/#"},{debug,"tmp/topic_trace.log"}}], emqx_tracer:lookup_traces()),
{{clientid,"client3"},{debug,"tmp/client3.log"}},
{{topic,"a/#"},{debug,"tmp/topic_trace.log"}},
{{topic,"b/#"},{debug,"tmp/topic_trace.log"}}], emqx_tracer:lookup_traces()),
%% set the overall log level to debug
emqx_logger:set_log_level(debug),
@ -77,23 +82,11 @@ t_start_traces(_Config) ->
%% Stop tracing
ok = emqx_tracer:stop_trace({clientid, <<"client">>}),
ok = emqx_tracer:stop_trace({clientid, <<"client2">>}),
ok = emqx_tracer:stop_trace({clientid, <<"client3">>}),
ok = emqx_tracer:stop_trace({topic, <<"a/#">>}),
ok = emqx_tracer:stop_trace({topic, <<"b/#">>}),
{error, _Reason} = emqx_tracer:stop_trace({topic, <<"c/#">>}),
emqtt:disconnect(T),
emqx_logger:set_log_level(warning).
t_start_trace(_) ->
error('TODO').
t_stop_trace(_) ->
error('TODO').
t_lookup_traces(_) ->
error('TODO').
t_trace(_) ->
error('TODO').

View File

@ -104,7 +104,8 @@ t_load(_Config) ->
t_systeminfo(_Config) ->
Keys = [Key || {Key, _} <- emqx_vm:get_system_info()],
?SYSTEM_INFO = Keys.
?SYSTEM_INFO = Keys,
?assertEqual(undefined, emqx_vm:get_system_info(undefined)).
t_mem_info(_Config) ->
application:ensure_all_started(os_mon),
@ -139,10 +140,19 @@ t_get_ets_info(_Config) ->
ets:new(test, [named_table]),
[] = emqx_vm:get_ets_info(test1),
EtsInfo = emqx_vm:get_ets_info(test),
test = proplists:get_value(name, EtsInfo).
test = proplists:get_value(name, EtsInfo),
Tid = proplists:get_value(id, EtsInfo),
EtsInfos = emqx_vm:get_ets_info(),
?assertEqual(true, lists:foldl(fun(Info, Acc) ->
case proplists:get_value(id, Info) of
Tid -> true;
_ -> Acc
end
end, false, EtsInfos)).
t_get_ets_object(_Config) ->
ets:new(test, [named_table]),
[] = emqx_vm:get_ets_object(test),
ets:insert(test, {k, v}),
[{k, v}] = emqx_vm:get_ets_object(test).
@ -150,7 +160,20 @@ t_get_port_types(_Config) ->
emqx_vm:get_port_types().
t_get_port_info(_Config) ->
emqx_vm:get_port_info().
emqx_vm:get_port_info(),
spawn(fun easy_server/0),
ct:sleep(100),
{ok, Sock} = gen_tcp:connect("localhost", 5678, [binary, {packet, 0}]),
emqx_vm:get_port_info(),
ok = gen_tcp:close(Sock),
[Port | _] = erlang:ports(),
[{connected, _}, {name, _}] = emqx_vm:port_info(Port, [connected, name]).
t_transform_port(_Config) ->
[Port | _] = erlang:ports(),
?assertEqual(Port, emqx_vm:transform_port(Port)),
<<131, 102, 100, NameLen:2/unit:8, _Name:NameLen/binary, N:4/unit:8, _Vsn:8>> = erlang:term_to_binary(Port),
?assertEqual(Port, emqx_vm:transform_port("#Port<0." ++ integer_to_list(N) ++ ">")).
t_scheduler_usage(_Config) ->
emqx_vm:scheduler_usage(5000).
@ -170,3 +193,21 @@ t_get_process_group_leader_info(_Config) ->
t_get_process_limit(_Config) ->
emqx_vm:get_process_limit().
t_cpu_util(_Config) ->
_Cpu = emqx_vm:cpu_util().
easy_server() ->
{ok, LSock} = gen_tcp:listen(5678, [binary, {packet, 0}, {active, false}]),
{ok, Sock} = gen_tcp:accept(LSock),
ok = do_recv(Sock),
ok = gen_tcp:close(Sock),
ok = gen_tcp:close(LSock).
do_recv(Sock) ->
case gen_tcp:recv(Sock, 0) of
{ok, _} ->
do_recv(Sock);
{error, closed} ->
ok
end.

View File

@ -39,18 +39,6 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
application:stop(sasl).
t_get_process_high_watermark(_) ->
error('TODO').
t_set_process_high_watermark(_) ->
error('TODO').
t_get_process_low_watermark(_) ->
error('TODO').
t_set_process_low_watermark(_) ->
error('TODO').
t_api(_) ->
meck:new(alarm_handler, [passthrough, no_history]),
Tester = self(),
@ -70,8 +58,13 @@ t_api(_) ->
end),
gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}),
{ok, _} = emqx_vm_mon:start_link([{check_interval, 1},
{process_high_watermark, 0},
{process_low_watermark, 0.6}]),
{process_high_watermark, 0.8},
{process_low_watermark, 0.75}]),
timer:sleep(emqx_vm_mon:get_check_interval() * 1000),
emqx_vm_mon:set_process_high_watermark(0.0),
emqx_vm_mon:set_process_low_watermark(0.6),
?assertEqual(0.0, emqx_vm_mon:get_process_high_watermark()),
?assertEqual(0.6, emqx_vm_mon:get_process_low_watermark()),
?WAIT({Ref, set_alarm, {too_many_processes, _Count}}, 2000),
?assertEqual(true, lists:keymember(too_many_processes, 1, alarm_handler:get_alarms())),
emqx_vm_mon:set_process_high_watermark(0.8),
@ -81,7 +74,10 @@ t_api(_) ->
?WAIT({Ref, clear_alarm, too_many_processes}, 3000),
?assertEqual(false, lists:keymember(too_many_processes, 1, alarm_handler:get_alarms())),
emqx_vm_mon:set_check_interval(20),
?assertEqual(20, emqx_vm_mon:get_check_interval())
?assertEqual(20, emqx_vm_mon:get_check_interval()),
?assertEqual(ignored, gen_server:call(emqx_vm_mon, ignored)),
?assertEqual(ok, gen_server:cast(emqx_vm_mon, ignored)),
?assertEqual(ignored, emqx_vm_mon ! ignored)
after
meck:unload(alarm_handler)
end.

View File

@ -52,10 +52,6 @@ init_per_testcase(_TestCase, Config) ->
ok = meck:expect(cowboy_req, parse_cookies, fun(_) -> undefined end),
%% Meck Channel
ok = meck:new(emqx_channel, [passthrough, no_history]),
ok = meck:expect(emqx_channel, recvd,
fun(_Oct, Channel) ->
{ok, Channel}
end),
%% Meck Metrics
ok = meck:new(emqx_metrics, [passthrough, no_history]),
ok = meck:expect(emqx_metrics, inc, fun(_, _) -> ok end),
@ -83,15 +79,7 @@ t_ws_conn_info(_) ->
#{socktype := ws,
peername := {{127,0,0,1}, 3456},
sockname := {{127,0,0,1}, 8883},
sockstate := idle} = SockInfo
end).
t_ws_conn_stats(_) ->
with_ws_conn(fun(WsConn) ->
Stats = emqx_ws_connection:stats(WsConn),
lists:foreach(fun(Key) ->
0 = proplists:get_value(Key, Stats)
end, ?STATS_KEYS)
sockstate := running} = SockInfo
end).
t_websocket_init(_) ->
@ -100,14 +88,13 @@ t_websocket_init(_) ->
#{socktype := ws,
peername := {{127,0,0,1}, 3456},
sockname := {{127,0,0,1}, 8883},
sockstate := idle
sockstate := running
} = SockInfo
end).
t_websocket_handle_binary(_) ->
with_ws_conn(fun(WsConn) ->
ok = meck:expect(emqx_channel, recvd, fun(_Oct, Channel) -> {ok, Channel} end),
{ok, WsConn} = websocket_handle({binary, [<<>>]}, WsConn)
{ok, _} = websocket_handle({binary, [<<>>]}, WsConn)
end).
t_websocket_handle_ping_pong(_) ->
@ -225,7 +212,7 @@ with_ws_conn(TestFun) ->
with_ws_conn(TestFun, []).
with_ws_conn(TestFun, Opts) ->
{ok, WsConn} = emqx_ws_connection:websocket_init(
{ok, WsConn, _} = emqx_ws_connection:websocket_init(
[req, emqx_misc:merge_opts([{zone, external}], Opts)]),
TestFun(WsConn).

View File

@ -56,6 +56,7 @@ init_per_suite(Config) ->
Config.
end_per_suite(_Config) ->
emqx_zone:unset_all_env(),
application:unset_env(emqx, zone_env),
application:unset_env(emqx, zones).
@ -99,3 +100,5 @@ t_uncovered_func(_) ->
ok = Pid ! ok,
emqx_zone:stop().
t_frame_options(_) ->
?assertMatch(#{strict_mode := _, max_size := _ }, emqx_zone:mqtt_frame_options(zone)).