Merge remote-tracking branch 'origin/develop'
This commit is contained in:
commit
a186ee71df
|
|
@ -1,7 +1,7 @@
|
||||||
language: erlang
|
language: erlang
|
||||||
|
|
||||||
otp_release:
|
otp_release:
|
||||||
- 21.3
|
- 22.1
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- git clone https://github.com/erlang/rebar3.git; cd rebar3; ./bootstrap; sudo mv rebar3 /usr/local/bin/; cd ..
|
- git clone https://github.com/erlang/rebar3.git; cd rebar3; ./bootstrap; sudo mv rebar3 /usr/local/bin/; cd ..
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -89,7 +89,7 @@ ct_setup:
|
||||||
|
|
||||||
.PHONY: ct
|
.PHONY: ct
|
||||||
ct: ct_setup
|
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
|
## Run one single CT with rebar3
|
||||||
## e.g. make ct-one-suite suite=emqx_bridge
|
## e.g. make ct-one-suite suite=emqx_bridge
|
||||||
|
|
|
||||||
|
|
@ -562,8 +562,8 @@ zone.external.hibernate_after = 60s
|
||||||
## Publish limit for the external MQTT connections.
|
## Publish limit for the external MQTT connections.
|
||||||
##
|
##
|
||||||
## Value: Number,Duration
|
## Value: Number,Duration
|
||||||
## Example: 10 messages per minute.
|
## Example: 100 messages per 10 seconds.
|
||||||
## zone.external.publish_limit = 10,1m
|
## zone.external.publish_limit = 100,10s
|
||||||
|
|
||||||
## Enable ACL check.
|
## Enable ACL check.
|
||||||
##
|
##
|
||||||
|
|
@ -874,14 +874,11 @@ listener.tcp.external.active_n = 100
|
||||||
## Value: String
|
## Value: String
|
||||||
listener.tcp.external.zone = external
|
listener.tcp.external.zone = external
|
||||||
|
|
||||||
## Rate limit for the external MQTT/TCP connections. Format is 'rate,burst'.
|
## Rate limit for the external MQTT/TCP connections. Format is 'limit,duration'.
|
||||||
##
|
##
|
||||||
## Value: rate,burst
|
## Value: limit,duration
|
||||||
## - rate: The average limit value for per second
|
## Default: 100KB incoming per 10 seconds.
|
||||||
## - burst: The maximum allowed for each check, To avoid frequent restriction
|
## listener.tcp.external.rate_limit = 100KB,10s
|
||||||
## this value is recommended to be set to `(max_packet_size * active_n)/2`
|
|
||||||
## Unit: Bps
|
|
||||||
## listener.tcp.external.rate_limit = 1024,52428800
|
|
||||||
|
|
||||||
## The access control rules for the MQTT/TCP listener.
|
## The access control rules for the MQTT/TCP listener.
|
||||||
##
|
##
|
||||||
|
|
@ -1010,12 +1007,9 @@ listener.tcp.internal.zone = internal
|
||||||
##
|
##
|
||||||
## See: listener.tcp.$name.rate_limit
|
## See: listener.tcp.$name.rate_limit
|
||||||
##
|
##
|
||||||
## Value: rate,burst
|
## Value: limit,duration
|
||||||
## - rate: The average limit value for per second
|
## Default: 1MB incoming per second.
|
||||||
## - burst: The maximum allowed for each check, To avoid frequent restriction
|
## listener.tcp.internal.rate_limit = 1MB,1s
|
||||||
## this value is recommended to be set to `(max_packet_size * active_n)/2`
|
|
||||||
## Unit: Bps
|
|
||||||
## listener.tcp.internal.rate_limit = 1000000,524288000
|
|
||||||
|
|
||||||
## The TCP backlog of internal MQTT/TCP Listener.
|
## The TCP backlog of internal MQTT/TCP Listener.
|
||||||
##
|
##
|
||||||
|
|
@ -1123,12 +1117,9 @@ listener.ssl.external.access.1 = allow all
|
||||||
|
|
||||||
## Rate limit for the external MQTT/SSL connections.
|
## Rate limit for the external MQTT/SSL connections.
|
||||||
##
|
##
|
||||||
## Value: rate,burst
|
## Value: limit,duration
|
||||||
## - rate: The average limit value for per second
|
## Default: 100KB incoming per 10 seconds.
|
||||||
## - burst: The maximum allowed for each check, To avoid frequent restriction
|
## listener.ssl.external.rate_limit = 100KB,10s
|
||||||
## this value is recommended to be set to `(max_packet_size * active_n)/2`
|
|
||||||
## Unit: Bps
|
|
||||||
## listener.ssl.external.rate_limit = 1024,52428800
|
|
||||||
|
|
||||||
## Enable the Proxy Protocol V1/2 if the EMQ cluster is deployed behind
|
## Enable the Proxy Protocol V1/2 if the EMQ cluster is deployed behind
|
||||||
## HAProxy or Nginx.
|
## HAProxy or Nginx.
|
||||||
|
|
@ -1358,14 +1349,16 @@ listener.ws.external.max_connections = 102400
|
||||||
## Value: Number
|
## Value: Number
|
||||||
listener.ws.external.max_conn_rate = 1000
|
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.
|
## Rate limit for the MQTT/WebSocket connections.
|
||||||
##
|
##
|
||||||
## Value: rate,burst
|
## Value: Limit,Duration
|
||||||
## - rate: The average limit value for per second
|
## Default: 100KB incoming per 10 seconds.
|
||||||
## - burst: The maximum allowed for each check, To avoid frequent restriction
|
## listener.ws.external.rate_limit = 100KB,10s
|
||||||
## this value is recommended to be set to `(max_packet_size * 1)/2`
|
|
||||||
## Unit: Bps
|
|
||||||
## listener.ws.external.rate_limit = 1024,524288
|
|
||||||
|
|
||||||
## Zone of the external MQTT/WebSocket listener belonged to.
|
## Zone of the external MQTT/WebSocket listener belonged to.
|
||||||
##
|
##
|
||||||
|
|
@ -1569,14 +1562,16 @@ listener.wss.external.max_connections = 16
|
||||||
## Value: Number
|
## Value: Number
|
||||||
listener.wss.external.max_conn_rate = 1000
|
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.
|
## Rate limit for the MQTT/WebSocket/SSL connections.
|
||||||
##
|
##
|
||||||
## Value: rate,burst
|
## Value: Limit,Duration
|
||||||
## - rate: The average limit value for per second
|
## Default: 100KB incoming per 10 seconds.
|
||||||
## - burst: The maximum allowed for each check, To avoid frequent restriction
|
## listener.wss.external.rate_limit = 100KB,10s
|
||||||
## this value is recommended to be set to `(max_packet_size * 1)/2`
|
|
||||||
## Unit: Bps
|
|
||||||
## listener.wss.external.rate_limit = 1024,524288
|
|
||||||
|
|
||||||
## Zone of the external MQTT/WebSocket/SSL listener belonged to.
|
## Zone of the external MQTT/WebSocket/SSL listener belonged to.
|
||||||
##
|
##
|
||||||
|
|
|
||||||
|
|
@ -939,14 +939,14 @@ end}.
|
||||||
("shared_subscription", Val) ->
|
("shared_subscription", Val) ->
|
||||||
{shared_subscription, Val};
|
{shared_subscription, Val};
|
||||||
("publish_limit", Val) ->
|
("publish_limit", Val) ->
|
||||||
[Limit, Duration] = string:tokens(Val, ", "),
|
[L, D] = string:tokens(Val, ", "),
|
||||||
PubLimit = case cuttlefish_duration:parse(Duration, s) of
|
Limit = list_to_integer(L),
|
||||||
Secs when is_integer(Secs) ->
|
Duration = case cuttlefish_duration:parse(D, s) of
|
||||||
{list_to_integer(Limit) / Secs, list_to_integer(Limit)};
|
Secs when is_integer(Secs) -> Secs;
|
||||||
{error, Reason} ->
|
{error, Reason} -> error(Reason)
|
||||||
error(Reason)
|
|
||||||
end,
|
end,
|
||||||
{publish_limit, PubLimit};
|
Rate = Limit / Duration,
|
||||||
|
{publish_limit, {Rate, Limit}};
|
||||||
("force_gc_policy", Val) ->
|
("force_gc_policy", Val) ->
|
||||||
[Count, Bytes] = string:tokens(Val, "| "),
|
[Count, Bytes] = string:tokens(Val, "| "),
|
||||||
GcPolicy = case cuttlefish_bytesize:parse(Bytes) of
|
GcPolicy = case cuttlefish_bytesize:parse(Bytes) of
|
||||||
|
|
@ -959,17 +959,18 @@ end}.
|
||||||
{force_gc_policy, GcPolicy};
|
{force_gc_policy, GcPolicy};
|
||||||
("force_shutdown_policy", "default") ->
|
("force_shutdown_policy", "default") ->
|
||||||
{DefaultLen, DefaultSize} =
|
{DefaultLen, DefaultSize} =
|
||||||
case erlang:system_info(wordsize) of
|
case WordSize = erlang:system_info(wordsize) of
|
||||||
8 -> % arch_64
|
8 -> % arch_64
|
||||||
{8000, cuttlefish_bytesize:parse("800MB")};
|
{8000, cuttlefish_bytesize:parse("800MB")};
|
||||||
4 -> % arch_32
|
4 -> % arch_32
|
||||||
{1000, cuttlefish_bytesize:parse("100MB")}
|
{1000, cuttlefish_bytesize:parse("100MB")}
|
||||||
end,
|
end,
|
||||||
{force_shutdown_policy, #{message_queue_len => DefaultLen,
|
{force_shutdown_policy, #{message_queue_len => DefaultLen,
|
||||||
max_heap_size => DefaultSize}};
|
max_heap_size => DefaultSize div WordSize
|
||||||
|
}};
|
||||||
("force_shutdown_policy", Val) ->
|
("force_shutdown_policy", Val) ->
|
||||||
[Len, Siz] = string:tokens(Val, "| "),
|
[Len, Siz] = string:tokens(Val, "| "),
|
||||||
MaxSiz = case erlang:system_info(wordsize) of
|
MaxSiz = case WordSize = erlang:system_info(wordsize) of
|
||||||
8 -> % arch_64
|
8 -> % arch_64
|
||||||
(1 bsl 59) - 1;
|
(1 bsl 59) - 1;
|
||||||
4 -> % arch_32
|
4 -> % arch_32
|
||||||
|
|
@ -983,7 +984,7 @@ end}.
|
||||||
cuttlefish:invalid(io_lib:format("force_shutdown_policy: heap-size ~s is too large", [Siz]));
|
cuttlefish:invalid(io_lib:format("force_shutdown_policy: heap-size ~s is too large", [Siz]));
|
||||||
Siz1 ->
|
Siz1 ->
|
||||||
#{message_queue_len => list_to_integer(Len),
|
#{message_queue_len => list_to_integer(Len),
|
||||||
max_heap_size => Siz1}
|
max_heap_size => Siz1 div WordSize}
|
||||||
end,
|
end,
|
||||||
{force_shutdown_policy, ShutdownPolicy};
|
{force_shutdown_policy, ShutdownPolicy};
|
||||||
("mqueue_priorities", Val) ->
|
("mqueue_priorities", Val) ->
|
||||||
|
|
@ -1289,6 +1290,11 @@ end}.
|
||||||
{datatype, integer}
|
{datatype, integer}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
{mapping, "listener.ws.$name.active_n", "emqx.listeners", [
|
||||||
|
{default, 100},
|
||||||
|
{datatype, integer}
|
||||||
|
]}.
|
||||||
|
|
||||||
{mapping, "listener.ws.$name.zone", "emqx.listeners", [
|
{mapping, "listener.ws.$name.zone", "emqx.listeners", [
|
||||||
{datatype, string}
|
{datatype, string}
|
||||||
]}.
|
]}.
|
||||||
|
|
@ -1442,6 +1448,11 @@ end}.
|
||||||
{datatype, integer}
|
{datatype, integer}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
|
{mapping, "listener.wss.$name.active_n", "emqx.listeners", [
|
||||||
|
{default, 100},
|
||||||
|
{datatype, integer}
|
||||||
|
]}.
|
||||||
|
|
||||||
{mapping, "listener.wss.$name.zone", "emqx.listeners", [
|
{mapping, "listener.wss.$name.zone", "emqx.listeners", [
|
||||||
{datatype, string}
|
{datatype, string}
|
||||||
]}.
|
]}.
|
||||||
|
|
@ -1644,10 +1655,20 @@ end}.
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Ratelimit = fun(undefined) ->
|
RateLimit = fun(undefined) ->
|
||||||
undefined;
|
undefined;
|
||||||
(S) ->
|
(Val) ->
|
||||||
list_to_tuple([list_to_integer(Token) || Token <- string:tokens(S, ",")])
|
[L, D] = string:tokens(Val, ", "),
|
||||||
|
Limit = case cuttlefish_bytesize:parse(L) of
|
||||||
|
Sz when is_integer(Sz) -> Sz;
|
||||||
|
{error, Reason} -> error(Reason)
|
||||||
|
end,
|
||||||
|
Duration = case cuttlefish_duration:parse(D, s) of
|
||||||
|
Secs when is_integer(Secs) -> Secs;
|
||||||
|
{error, Reason1} -> error(Reason1)
|
||||||
|
end,
|
||||||
|
Rate = Limit / Duration,
|
||||||
|
{Rate, Limit}
|
||||||
end,
|
end,
|
||||||
|
|
||||||
LisOpts = fun(Prefix) ->
|
LisOpts = fun(Prefix) ->
|
||||||
|
|
@ -1658,7 +1679,7 @@ end}.
|
||||||
{active_n, cuttlefish:conf_get(Prefix ++ ".active_n", Conf, undefined)},
|
{active_n, cuttlefish:conf_get(Prefix ++ ".active_n", Conf, undefined)},
|
||||||
{tune_buffer, cuttlefish:conf_get(Prefix ++ ".tune_buffer", Conf, undefined)},
|
{tune_buffer, cuttlefish:conf_get(Prefix ++ ".tune_buffer", Conf, undefined)},
|
||||||
{zone, Atom(cuttlefish:conf_get(Prefix ++ ".zone", Conf, undefined))},
|
{zone, Atom(cuttlefish:conf_get(Prefix ++ ".zone", Conf, undefined))},
|
||||||
{rate_limit, Ratelimit(cuttlefish:conf_get(Prefix ++ ".rate_limit", Conf, undefined))},
|
{rate_limit, RateLimit(cuttlefish:conf_get(Prefix ++ ".rate_limit", Conf, undefined))},
|
||||||
{proxy_protocol, cuttlefish:conf_get(Prefix ++ ".proxy_protocol", Conf, undefined)},
|
{proxy_protocol, cuttlefish:conf_get(Prefix ++ ".proxy_protocol", Conf, undefined)},
|
||||||
{proxy_protocol_timeout, cuttlefish:conf_get(Prefix ++ ".proxy_protocol_timeout", Conf, undefined)},
|
{proxy_protocol_timeout, cuttlefish:conf_get(Prefix ++ ".proxy_protocol_timeout", Conf, undefined)},
|
||||||
{verify_protocol_header, cuttlefish:conf_get(Prefix ++ ".verify_protocol_header", Conf, undefined)},
|
{verify_protocol_header, cuttlefish:conf_get(Prefix ++ ".verify_protocol_header", Conf, undefined)},
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
[{jsx, "2.9.0"}, % hex
|
[{jsx, "2.9.0"}, % hex
|
||||||
{cowboy, "2.6.1"}, % hex
|
{cowboy, "2.6.1"}, % hex
|
||||||
{gproc, "0.8.0"}, % 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"}}},
|
{ekka, {git, "https://github.com/emqx/ekka", {tag, "v0.6.2"}}},
|
||||||
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.4.1"}}},
|
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.4.1"}}},
|
||||||
{cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v3.0.0"}}}
|
{cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v3.0.0"}}}
|
||||||
|
|
|
||||||
|
|
@ -18,21 +18,31 @@
|
||||||
|
|
||||||
-include("emqx.hrl").
|
-include("emqx.hrl").
|
||||||
|
|
||||||
-export([ get_acl_cache/2
|
-export([ list_acl_cache/0
|
||||||
|
, get_acl_cache/2
|
||||||
, put_acl_cache/3
|
, put_acl_cache/3
|
||||||
, cleanup_acl_cache/0
|
, cleanup_acl_cache/0
|
||||||
, empty_acl_cache/0
|
, empty_acl_cache/0
|
||||||
, dump_acl_cache/0
|
, dump_acl_cache/0
|
||||||
, get_cache_size/0
|
|
||||||
, get_cache_max_size/0
|
, get_cache_max_size/0
|
||||||
, get_newest_key/0
|
, get_cache_ttl/0
|
||||||
, get_oldest_key/0
|
|
||||||
, cache_k/2
|
|
||||||
, cache_v/1
|
|
||||||
, is_enabled/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(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
|
%% Wrappers for key and value
|
||||||
cache_k(PubSub, Topic)-> {PubSub, Topic}.
|
cache_k(PubSub, Topic)-> {PubSub, Topic}.
|
||||||
|
|
@ -42,8 +52,21 @@ cache_v(AclResult)-> {AclResult, time_now()}.
|
||||||
is_enabled() ->
|
is_enabled() ->
|
||||||
application:get_env(emqx, enable_acl_cache, true).
|
application:get_env(emqx, enable_acl_cache, true).
|
||||||
|
|
||||||
%% We'll cleanup the cache before repalcing an expired acl.
|
-spec(get_cache_max_size() -> integer()).
|
||||||
-spec(get_acl_cache(publish | subscribe, emqx_topic:topic()) -> (acl_result() | not_found)).
|
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) ->
|
get_acl_cache(PubSub, Topic) ->
|
||||||
case erlang:get(cache_k(PubSub, Topic)) of
|
case erlang:get(cache_k(PubSub, Topic)) of
|
||||||
undefined -> not_found;
|
undefined -> not_found;
|
||||||
|
|
@ -59,7 +82,7 @@ get_acl_cache(PubSub, Topic) ->
|
||||||
|
|
||||||
%% If the cache get full, and also the latest one
|
%% If the cache get full, and also the latest one
|
||||||
%% is expired, then delete all the cache entries
|
%% 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) ->
|
put_acl_cache(PubSub, Topic, AclResult) ->
|
||||||
MaxSize = get_cache_max_size(), true = (MaxSize =/= 0),
|
MaxSize = get_cache_max_size(), true = (MaxSize =/= 0),
|
||||||
Size = get_cache_size(),
|
Size = get_cache_size(),
|
||||||
|
|
@ -97,7 +120,7 @@ evict_acl_cache() ->
|
||||||
erlang:erase(OldestK),
|
erlang:erase(OldestK),
|
||||||
decr_cache_size().
|
decr_cache_size().
|
||||||
|
|
||||||
%% cleanup all the exipired cache entries
|
%% cleanup all the expired cache entries
|
||||||
-spec(cleanup_acl_cache() -> ok).
|
-spec(cleanup_acl_cache() -> ok).
|
||||||
cleanup_acl_cache() ->
|
cleanup_acl_cache() ->
|
||||||
keys_queue_set(
|
keys_queue_set(
|
||||||
|
|
@ -108,9 +131,6 @@ get_oldest_key() ->
|
||||||
get_newest_key() ->
|
get_newest_key() ->
|
||||||
keys_queue_pick(queue_rear()).
|
keys_queue_pick(queue_rear()).
|
||||||
|
|
||||||
get_cache_max_size() ->
|
|
||||||
application:get_env(emqx, acl_cache_max_size, 32).
|
|
||||||
|
|
||||||
get_cache_size() ->
|
get_cache_size() ->
|
||||||
case erlang:get(acl_cache_size) of
|
case erlang:get(acl_cache_size) of
|
||||||
undefined -> 0;
|
undefined -> 0;
|
||||||
|
|
@ -215,7 +235,7 @@ queue_rear() -> fun queue:get_r/1.
|
||||||
time_now() -> erlang:system_time(millisecond).
|
time_now() -> erlang:system_time(millisecond).
|
||||||
|
|
||||||
if_expired(CachedAt, Fun) ->
|
if_expired(CachedAt, Fun) ->
|
||||||
TTL = application:get_env(emqx, acl_cache_ttl, 60000),
|
TTL = get_cache_ttl(),
|
||||||
Now = time_now(),
|
Now = time_now(),
|
||||||
if (CachedAt + TTL) =< Now ->
|
if (CachedAt + TTL) =< Now ->
|
||||||
Fun(true);
|
Fun(true);
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ encode_alarm({AlarmId, #alarm{severity = Severity,
|
||||||
{desc, [{severity, Severity},
|
{desc, [{severity, Severity},
|
||||||
{title, iolist_to_binary(Title)},
|
{title, iolist_to_binary(Title)},
|
||||||
{summary, iolist_to_binary(Summary)},
|
{summary, iolist_to_binary(Summary)},
|
||||||
{timestamp, emqx_time:now_ms(Ts)}]}]);
|
{timestamp, emqx_misc:now_to_secs(Ts)}]}]);
|
||||||
encode_alarm({AlarmId, undefined}) ->
|
encode_alarm({AlarmId, undefined}) ->
|
||||||
emqx_json:safe_encode([{id, maybe_to_binary(AlarmId)}]);
|
emqx_json:safe_encode([{id, maybe_to_binary(AlarmId)}]);
|
||||||
encode_alarm({AlarmId, AlarmDesc}) ->
|
encode_alarm({AlarmId, AlarmDesc}) ->
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,7 @@ aggre(Routes) ->
|
||||||
-spec(forward(node(), emqx_types:topic(), emqx_types:delivery(), RPCMode::sync|async)
|
-spec(forward(node(), emqx_types:topic(), emqx_types:delivery(), RPCMode::sync|async)
|
||||||
-> emqx_types:deliver_result()).
|
-> emqx_types:deliver_result()).
|
||||||
forward(Node, To, Delivery, async) ->
|
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;
|
true -> ok;
|
||||||
{badrpc, Reason} ->
|
{badrpc, Reason} ->
|
||||||
?LOG(error, "Ansync forward msg to ~s failed: ~p", [Node, Reason]),
|
?LOG(error, "Ansync forward msg to ~s failed: ~p", [Node, Reason]),
|
||||||
|
|
@ -266,7 +266,7 @@ forward(Node, To, Delivery, async) ->
|
||||||
end;
|
end;
|
||||||
|
|
||||||
forward(Node, To, Delivery, sync) ->
|
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} ->
|
{badrpc, Reason} ->
|
||||||
?LOG(error, "Sync forward msg to ~s failed: ~p", [Node, Reason]),
|
?LOG(error, "Sync forward msg to ~s failed: ~p", [Node, Reason]),
|
||||||
{error, badrpc};
|
{error, badrpc};
|
||||||
|
|
@ -486,3 +486,4 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,11 @@
|
||||||
, code_change/3
|
, code_change/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
-endif.
|
||||||
|
|
||||||
-define(HELPER, ?MODULE).
|
-define(HELPER, ?MODULE).
|
||||||
-define(SUBID, emqx_subid).
|
-define(SUBID, emqx_subid).
|
||||||
-define(SUBMON, emqx_submon).
|
-define(SUBMON, emqx_submon).
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,11 @@
|
||||||
|
|
||||||
-logger_header("[Channel]").
|
-logger_header("[Channel]").
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
-endif.
|
||||||
|
|
||||||
-export([ info/1
|
-export([ info/1
|
||||||
, info/2
|
, info/2
|
||||||
, attrs/1
|
, attrs/1
|
||||||
|
|
@ -31,18 +36,18 @@
|
||||||
, caps/1
|
, caps/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Test Exports
|
|
||||||
-export([set_field/3]).
|
|
||||||
|
|
||||||
-export([ init/2
|
-export([ init/2
|
||||||
, handle_in/2
|
, handle_in/2
|
||||||
, handle_out/2
|
, handle_out/2
|
||||||
, handle_call/2
|
, handle_call/2
|
||||||
, handle_info/2
|
|
||||||
, handle_timeout/3
|
, handle_timeout/3
|
||||||
|
, handle_info/2
|
||||||
, terminate/2
|
, terminate/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
%% export for ct
|
||||||
|
-export([set_field/3]).
|
||||||
|
|
||||||
-import(emqx_misc,
|
-import(emqx_misc,
|
||||||
[ run_fold/3
|
[ run_fold/3
|
||||||
, pipeline/3
|
, pipeline/3
|
||||||
|
|
@ -66,14 +71,10 @@
|
||||||
topic_aliases :: maybe(map()),
|
topic_aliases :: maybe(map()),
|
||||||
%% MQTT Topic Alias Maximum
|
%% MQTT Topic Alias Maximum
|
||||||
alias_maximum :: maybe(map()),
|
alias_maximum :: maybe(map()),
|
||||||
%% Publish Stats
|
|
||||||
pub_stats :: emqx_types:stats(),
|
|
||||||
%% Timers
|
%% Timers
|
||||||
timers :: #{atom() => disabled | maybe(reference())},
|
timers :: #{atom() => disabled | maybe(reference())},
|
||||||
%% Conn State
|
%% Conn State
|
||||||
conn_state :: conn_state(),
|
conn_state :: conn_state(),
|
||||||
%% GC State
|
|
||||||
gc_state :: maybe(emqx_gc:gc_state()),
|
|
||||||
%% Takeover
|
%% Takeover
|
||||||
takeover :: boolean(),
|
takeover :: boolean(),
|
||||||
%% Resume
|
%% Resume
|
||||||
|
|
@ -94,7 +95,6 @@
|
||||||
-type(output() :: emqx_types:packet() | action() | [action()]).
|
-type(output() :: emqx_types:packet() | action() | [action()]).
|
||||||
|
|
||||||
-define(TIMER_TABLE, #{
|
-define(TIMER_TABLE, #{
|
||||||
stats_timer => emit_stats,
|
|
||||||
alive_timer => keepalive,
|
alive_timer => keepalive,
|
||||||
retry_timer => retry_delivery,
|
retry_timer => retry_delivery,
|
||||||
await_timer => expire_awaiting_rel,
|
await_timer => expire_awaiting_rel,
|
||||||
|
|
@ -104,8 +104,7 @@
|
||||||
|
|
||||||
-define(ATTR_KEYS, [conninfo, clientinfo, session, conn_state]).
|
-define(ATTR_KEYS, [conninfo, clientinfo, session, conn_state]).
|
||||||
|
|
||||||
-define(INFO_KEYS, ?ATTR_KEYS ++ [keepalive, will_msg, topic_aliases,
|
-define(INFO_KEYS, ?ATTR_KEYS ++ [keepalive, will_msg, topic_aliases, alias_maximum]).
|
||||||
alias_maximum, gc_state]).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Info, Attrs and Caps
|
%% Info, Attrs and Caps
|
||||||
|
|
@ -137,10 +136,8 @@ info(will_msg, #channel{will_msg = undefined}) ->
|
||||||
undefined;
|
undefined;
|
||||||
info(will_msg, #channel{will_msg = WillMsg}) ->
|
info(will_msg, #channel{will_msg = WillMsg}) ->
|
||||||
emqx_message:to_map(WillMsg);
|
emqx_message:to_map(WillMsg);
|
||||||
info(pub_stats, #channel{pub_stats = PubStats}) ->
|
info(timers, #channel{timers = Timers}) ->
|
||||||
PubStats;
|
Timers.
|
||||||
info(gc_state, #channel{gc_state = GcState}) ->
|
|
||||||
maybe_apply(fun emqx_gc:info/1, GcState).
|
|
||||||
|
|
||||||
%% @doc Get attrs of the channel.
|
%% @doc Get attrs of the channel.
|
||||||
-spec(attrs(channel()) -> emqx_types:attrs()).
|
-spec(attrs(channel()) -> emqx_types:attrs()).
|
||||||
|
|
@ -153,10 +150,8 @@ attrs(session, #channel{session = Session}) ->
|
||||||
attrs(Key, Channel) -> info(Key, Channel).
|
attrs(Key, Channel) -> info(Key, Channel).
|
||||||
|
|
||||||
-spec(stats(channel()) -> emqx_types:stats()).
|
-spec(stats(channel()) -> emqx_types:stats()).
|
||||||
stats(#channel{pub_stats = PubStats, session = undefined}) ->
|
stats(#channel{session = Session})->
|
||||||
maps:to_list(PubStats);
|
emqx_session:stats(Session).
|
||||||
stats(#channel{pub_stats = PubStats, session = Session}) ->
|
|
||||||
maps:to_list(PubStats) ++ emqx_session:stats(Session).
|
|
||||||
|
|
||||||
-spec(caps(channel()) -> emqx_types:caps()).
|
-spec(caps(channel()) -> emqx_types:caps()).
|
||||||
caps(#channel{clientinfo = #{zone := Zone}}) ->
|
caps(#channel{clientinfo = #{zone := Zone}}) ->
|
||||||
|
|
@ -183,7 +178,7 @@ init(ConnInfo = #{peername := {PeerHost, _Port}}, Options) ->
|
||||||
_ -> undefined
|
_ -> undefined
|
||||||
end,
|
end,
|
||||||
Protocol = maps:get(protocol, ConnInfo, mqtt),
|
Protocol = maps:get(protocol, ConnInfo, mqtt),
|
||||||
MountPoint = emqx_zone:get_env(Zone, mountpoint),
|
MountPoint = emqx_zone:mountpoint(Zone),
|
||||||
ClientInfo = #{zone => Zone,
|
ClientInfo = #{zone => Zone,
|
||||||
protocol => Protocol,
|
protocol => Protocol,
|
||||||
peerhost => PeerHost,
|
peerhost => PeerHost,
|
||||||
|
|
@ -194,16 +189,10 @@ init(ConnInfo = #{peername := {PeerHost, _Port}}, Options) ->
|
||||||
is_bridge => false,
|
is_bridge => false,
|
||||||
is_superuser => false
|
is_superuser => false
|
||||||
},
|
},
|
||||||
StatsTimer = case emqx_zone:enable_stats(Zone) of
|
|
||||||
true -> undefined;
|
|
||||||
false -> disabled
|
|
||||||
end,
|
|
||||||
#channel{conninfo = ConnInfo,
|
#channel{conninfo = ConnInfo,
|
||||||
clientinfo = ClientInfo,
|
clientinfo = ClientInfo,
|
||||||
pub_stats = #{},
|
timers = #{},
|
||||||
timers = #{stats_timer => StatsTimer},
|
|
||||||
conn_state = idle,
|
conn_state = idle,
|
||||||
gc_state = init_gc_state(Zone),
|
|
||||||
takeover = false,
|
takeover = false,
|
||||||
resuming = false,
|
resuming = false,
|
||||||
pendings = []
|
pendings = []
|
||||||
|
|
@ -212,24 +201,17 @@ init(ConnInfo = #{peername := {PeerHost, _Port}}, Options) ->
|
||||||
peer_cert_as_username(Options) ->
|
peer_cert_as_username(Options) ->
|
||||||
proplists:get_value(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
|
%% Handle incoming packet
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-spec(handle_in(Bytes :: pos_integer() | emqx_types:packet(), channel())
|
-spec(handle_in(emqx_types:packet(), channel())
|
||||||
-> {ok, channel()}
|
-> {ok, channel()}
|
||||||
| {ok, output(), channel()}
|
| {ok, output(), channel()}
|
||||||
| {stop, Reason :: term(), channel()}
|
| {shutdown, Reason :: term(), channel()}
|
||||||
| {stop, Reason :: term(), output(), channel()}).
|
| {shutdown, Reason :: term(), output(), channel()}).
|
||||||
handle_in(Bytes, Channel) when is_integer(Bytes) ->
|
|
||||||
NChannel = maybe_gc_and_check_oom(Bytes, Channel),
|
|
||||||
{ok, ensure_timer(stats_timer, NChannel)};
|
|
||||||
|
|
||||||
handle_in(?CONNECT_PACKET(_), Channel = #channel{conn_state = connected}) ->
|
handle_in(?CONNECT_PACKET(_), Channel = #channel{conn_state = connected}) ->
|
||||||
handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel);
|
handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel);
|
||||||
|
|
||||||
handle_in(?CONNECT_PACKET(ConnPkt), Channel) ->
|
handle_in(?CONNECT_PACKET(ConnPkt), Channel) ->
|
||||||
case pipeline([fun enrich_conninfo/2,
|
case pipeline([fun enrich_conninfo/2,
|
||||||
|
|
@ -243,77 +225,73 @@ handle_in(?CONNECT_PACKET(ConnPkt), Channel) ->
|
||||||
process_connect(NConnPkt, NChannel);
|
process_connect(NConnPkt, NChannel);
|
||||||
{error, ReasonCode, NChannel} ->
|
{error, ReasonCode, NChannel} ->
|
||||||
ReasonName = emqx_reason_codes:formalized(connack, ReasonCode),
|
ReasonName = emqx_reason_codes:formalized(connack, ReasonCode),
|
||||||
handle_out({connack, ReasonName, ConnPkt}, NChannel)
|
handle_out(connack, {ReasonName, ConnPkt}, NChannel)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_in(Packet = ?PUBLISH_PACKET(_QoS), Channel) ->
|
handle_in(Packet = ?PUBLISH_PACKET(_QoS), Channel) ->
|
||||||
NChannel = inc_pub_stats(publish_in, Channel),
|
|
||||||
case emqx_packet:check(Packet) of
|
case emqx_packet:check(Packet) of
|
||||||
ok -> handle_publish(Packet, NChannel);
|
ok -> handle_publish(Packet, Channel);
|
||||||
{error, ReasonCode} ->
|
{error, ReasonCode} ->
|
||||||
handle_out({disconnect, ReasonCode}, NChannel)
|
handle_out(disconnect, ReasonCode, Channel)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_in(?PUBACK_PACKET(PacketId, _ReasonCode),
|
handle_in(?PUBACK_PACKET(PacketId, _ReasonCode),
|
||||||
Channel = #channel{clientinfo = ClientInfo, session = Session}) ->
|
Channel = #channel{clientinfo = ClientInfo, session = Session}) ->
|
||||||
NChannel = inc_pub_stats(puback_in, Channel),
|
|
||||||
case emqx_session:puback(PacketId, Session) of
|
case emqx_session:puback(PacketId, Session) of
|
||||||
{ok, Msg, Publishes, NSession} ->
|
{ok, Msg, Publishes, NSession} ->
|
||||||
ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
|
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, Msg, NSession} ->
|
||||||
ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
|
ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
|
||||||
{ok, NChannel#channel{session = NSession}};
|
{ok, Channel#channel{session = NSession}};
|
||||||
{error, ?RC_PACKET_IDENTIFIER_IN_USE} ->
|
{error, ?RC_PACKET_IDENTIFIER_IN_USE} ->
|
||||||
?LOG(warning, "The PUBACK PacketId ~w is inuse.", [PacketId]),
|
?LOG(warning, "The PUBACK PacketId ~w is inuse.", [PacketId]),
|
||||||
ok = emqx_metrics:inc('packets.puback.inuse'),
|
ok = emqx_metrics:inc('packets.puback.inuse'),
|
||||||
{ok, NChannel};
|
{ok, Channel};
|
||||||
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} ->
|
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} ->
|
||||||
?LOG(warning, "The PUBACK PacketId ~w is not found", [PacketId]),
|
?LOG(warning, "The PUBACK PacketId ~w is not found", [PacketId]),
|
||||||
ok = emqx_metrics:inc('packets.puback.missed'),
|
ok = emqx_metrics:inc('packets.puback.missed'),
|
||||||
{ok, NChannel}
|
{ok, Channel}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_in(?PUBREC_PACKET(PacketId, _ReasonCode),
|
handle_in(?PUBREC_PACKET(PacketId, _ReasonCode),
|
||||||
Channel = #channel{clientinfo = ClientInfo, session = Session}) ->
|
Channel = #channel{clientinfo = ClientInfo, session = Session}) ->
|
||||||
Channel1 = inc_pub_stats(pubrec_in, Channel),
|
|
||||||
case emqx_session:pubrec(PacketId, Session) of
|
case emqx_session:pubrec(PacketId, Session) of
|
||||||
{ok, Msg, NSession} ->
|
{ok, Msg, NSession} ->
|
||||||
ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]),
|
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);
|
handle_out(pubrel, {PacketId, ?RC_SUCCESS}, NChannel);
|
||||||
{error, RC = ?RC_PACKET_IDENTIFIER_IN_USE} ->
|
{error, RC = ?RC_PACKET_IDENTIFIER_IN_USE} ->
|
||||||
?LOG(warning, "The PUBREC PacketId ~w is inuse.", [PacketId]),
|
?LOG(warning, "The PUBREC PacketId ~w is inuse.", [PacketId]),
|
||||||
ok = emqx_metrics:inc('packets.pubrec.inuse'),
|
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} ->
|
{error, RC = ?RC_PACKET_IDENTIFIER_NOT_FOUND} ->
|
||||||
?LOG(warning, "The PUBREC ~w is not found.", [PacketId]),
|
?LOG(warning, "The PUBREC ~w is not found.", [PacketId]),
|
||||||
ok = emqx_metrics:inc('packets.pubrec.missed'),
|
ok = emqx_metrics:inc('packets.pubrec.missed'),
|
||||||
handle_out({pubrel, PacketId, RC}, Channel1)
|
handle_out(pubrel, {PacketId, RC}, Channel)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_in(?PUBREL_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) ->
|
handle_in(?PUBREL_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) ->
|
||||||
Channel1 = inc_pub_stats(pubrel_in, Channel),
|
|
||||||
case emqx_session:pubrel(PacketId, Session) of
|
case emqx_session:pubrel(PacketId, Session) of
|
||||||
{ok, NSession} ->
|
{ok, NSession} ->
|
||||||
handle_out({pubcomp, PacketId, ?RC_SUCCESS}, Channel1#channel{session = NSession});
|
NChannel = Channel#channel{session = NSession},
|
||||||
|
handle_out(pubcomp, {PacketId, ?RC_SUCCESS}, NChannel);
|
||||||
{error, NotFound} ->
|
{error, NotFound} ->
|
||||||
ok = emqx_metrics:inc('packets.pubrel.missed'),
|
ok = emqx_metrics:inc('packets.pubrel.missed'),
|
||||||
?LOG(warning, "The PUBREL PacketId ~w is not found", [PacketId]),
|
?LOG(warning, "The PUBREL PacketId ~w is not found", [PacketId]),
|
||||||
handle_out({pubcomp, PacketId, NotFound}, Channel1)
|
handle_out(pubcomp, {PacketId, NotFound}, Channel)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_in(?PUBCOMP_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) ->
|
handle_in(?PUBCOMP_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) ->
|
||||||
Channel1 = inc_pub_stats(pubcomp_in, Channel),
|
|
||||||
case emqx_session:pubcomp(PacketId, Session) of
|
case emqx_session:pubcomp(PacketId, Session) of
|
||||||
{ok, Publishes, NSession} ->
|
{ok, Publishes, NSession} ->
|
||||||
handle_out({publish, Publishes}, Channel1#channel{session = NSession});
|
handle_out({publish, Publishes}, Channel#channel{session = NSession});
|
||||||
{ok, NSession} ->
|
{ok, NSession} ->
|
||||||
{ok, Channel1#channel{session = NSession}};
|
{ok, Channel#channel{session = NSession}};
|
||||||
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} ->
|
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} ->
|
||||||
?LOG(warning, "The PUBCOMP PacketId ~w is not found", [PacketId]),
|
?LOG(warning, "The PUBCOMP PacketId ~w is not found", [PacketId]),
|
||||||
ok = emqx_metrics:inc('packets.pubcomp.missed'),
|
ok = emqx_metrics:inc('packets.pubcomp.missed'),
|
||||||
{ok, Channel1}
|
{ok, Channel}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
|
handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
|
||||||
|
|
@ -324,9 +302,9 @@ handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
|
||||||
parse_topic_filters(TopicFilters)),
|
parse_topic_filters(TopicFilters)),
|
||||||
TopicFilters2 = enrich_subid(Properties, TopicFilters1),
|
TopicFilters2 = enrich_subid(Properties, TopicFilters1),
|
||||||
{ReasonCodes, NChannel} = process_subscribe(TopicFilters2, Channel),
|
{ReasonCodes, NChannel} = process_subscribe(TopicFilters2, Channel),
|
||||||
handle_out({suback, PacketId, ReasonCodes}, NChannel);
|
handle_out(suback, {PacketId, ReasonCodes}, NChannel);
|
||||||
{error, ReasonCode} ->
|
{error, ReasonCode} ->
|
||||||
handle_out({disconnect, ReasonCode}, Channel)
|
handle_out(disconnect, ReasonCode, Channel)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_in(Packet = ?UNSUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
|
handle_in(Packet = ?UNSUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
|
||||||
|
|
@ -336,9 +314,9 @@ handle_in(Packet = ?UNSUBSCRIBE_PACKET(PacketId, Properties, TopicFilters),
|
||||||
[ClientInfo, Properties],
|
[ClientInfo, Properties],
|
||||||
parse_topic_filters(TopicFilters)),
|
parse_topic_filters(TopicFilters)),
|
||||||
{ReasonCodes, NChannel} = process_unsubscribe(TopicFilters1, Channel),
|
{ReasonCodes, NChannel} = process_unsubscribe(TopicFilters1, Channel),
|
||||||
handle_out({unsuback, PacketId, ReasonCodes}, NChannel);
|
handle_out(unsuback, {PacketId, ReasonCodes}, NChannel);
|
||||||
{error, ReasonCode} ->
|
{error, ReasonCode} ->
|
||||||
handle_out({disconnect, ReasonCode}, Channel)
|
handle_out(disconnect, ReasonCode, Channel)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_in(?PACKET(?PINGREQ), Channel) ->
|
handle_in(?PACKET(?PINGREQ), Channel) ->
|
||||||
|
|
@ -355,7 +333,7 @@ handle_in(?DISCONNECT_PACKET(ReasonCode, Properties), Channel = #channel{conninf
|
||||||
Interval = emqx_mqtt_props:get('Session-Expiry-Interval', Properties, OldInterval),
|
Interval = emqx_mqtt_props:get('Session-Expiry-Interval', Properties, OldInterval),
|
||||||
if
|
if
|
||||||
OldInterval == 0 andalso Interval > OldInterval ->
|
OldInterval == 0 andalso Interval > OldInterval ->
|
||||||
handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel1);
|
handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel1);
|
||||||
Interval == 0 ->
|
Interval == 0 ->
|
||||||
shutdown(ReasonName, Channel1);
|
shutdown(ReasonName, Channel1);
|
||||||
true ->
|
true ->
|
||||||
|
|
@ -364,7 +342,7 @@ handle_in(?DISCONNECT_PACKET(ReasonCode, Properties), Channel = #channel{conninf
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_in(?AUTH_PACKET(), Channel) ->
|
handle_in(?AUTH_PACKET(), Channel) ->
|
||||||
handle_out({disconnect, ?RC_IMPLEMENTATION_SPECIFIC_ERROR}, Channel);
|
handle_out(disconnect, ?RC_IMPLEMENTATION_SPECIFIC_ERROR, Channel);
|
||||||
|
|
||||||
handle_in({frame_error, Reason}, Channel = #channel{conn_state = idle}) ->
|
handle_in({frame_error, Reason}, Channel = #channel{conn_state = idle}) ->
|
||||||
shutdown(Reason, Channel);
|
shutdown(Reason, Channel);
|
||||||
|
|
@ -373,7 +351,7 @@ handle_in({frame_error, Reason}, Channel = #channel{conn_state = connecting}) ->
|
||||||
shutdown(Reason, ?CONNACK_PACKET(?RC_MALFORMED_PACKET), Channel);
|
shutdown(Reason, ?CONNACK_PACKET(?RC_MALFORMED_PACKET), Channel);
|
||||||
|
|
||||||
handle_in({frame_error, _Reason}, Channel = #channel{conn_state = connected}) ->
|
handle_in({frame_error, _Reason}, Channel = #channel{conn_state = connected}) ->
|
||||||
handle_out({disconnect, ?RC_MALFORMED_PACKET}, Channel);
|
handle_out(disconnect, ?RC_MALFORMED_PACKET, Channel);
|
||||||
|
|
||||||
handle_in({frame_error, Reason}, Channel = #channel{conn_state = disconnected}) ->
|
handle_in({frame_error, Reason}, Channel = #channel{conn_state = disconnected}) ->
|
||||||
?LOG(error, "Unexpected frame error: ~p", [Reason]),
|
?LOG(error, "Unexpected frame error: ~p", [Reason]),
|
||||||
|
|
@ -381,7 +359,7 @@ handle_in({frame_error, Reason}, Channel = #channel{conn_state = disconnected})
|
||||||
|
|
||||||
handle_in(Packet, Channel) ->
|
handle_in(Packet, Channel) ->
|
||||||
?LOG(error, "Unexpected incoming: ~p", [Packet]),
|
?LOG(error, "Unexpected incoming: ~p", [Packet]),
|
||||||
handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel).
|
handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Process Connect
|
%% Process Connect
|
||||||
|
|
@ -392,29 +370,24 @@ process_connect(ConnPkt = #mqtt_packet_connect{clean_start = CleanStart},
|
||||||
case emqx_cm:open_session(CleanStart, ClientInfo, ConnInfo) of
|
case emqx_cm:open_session(CleanStart, ClientInfo, ConnInfo) of
|
||||||
{ok, #{session := Session, present := false}} ->
|
{ok, #{session := Session, present := false}} ->
|
||||||
NChannel = Channel#channel{session = Session},
|
NChannel = Channel#channel{session = Session},
|
||||||
handle_out({connack, ?RC_SUCCESS, sp(false), ConnPkt}, NChannel);
|
handle_out(connack, {?RC_SUCCESS, sp(false), ConnPkt}, NChannel);
|
||||||
{ok, #{session := Session, present := true, pendings := Pendings}} ->
|
{ok, #{session := Session, present := true, pendings := Pendings}} ->
|
||||||
%%TODO: improve later.
|
%%TODO: improve later.
|
||||||
NPendings = lists:usort(lists:append(Pendings, emqx_misc:drain_deliver())),
|
NPendings = lists:usort(lists:append(Pendings, emqx_misc:drain_deliver())),
|
||||||
NChannel = Channel#channel{session = Session,
|
NChannel = Channel#channel{session = Session,
|
||||||
resuming = true,
|
resuming = true,
|
||||||
pendings = NPendings},
|
pendings = NPendings},
|
||||||
handle_out({connack, ?RC_SUCCESS, sp(true), ConnPkt}, NChannel);
|
handle_out(connack, {?RC_SUCCESS, sp(true), ConnPkt}, NChannel);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
%% TODO: Unknown error?
|
%% TODO: Unknown error?
|
||||||
?LOG(error, "Failed to open session: ~p", [Reason]),
|
?LOG(error, "Failed to open session: ~p", [Reason]),
|
||||||
handle_out({connack, ?RC_UNSPECIFIED_ERROR, ConnPkt}, Channel)
|
handle_out(connack, {?RC_UNSPECIFIED_ERROR, ConnPkt}, Channel)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Process Publish
|
%% 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),
|
handle_publish(Packet = ?PUBLISH_PACKET(_QoS, Topic, _PacketId),
|
||||||
Channel = #channel{conninfo = #{proto_ver := ProtoVer}}) ->
|
Channel = #channel{conninfo = #{proto_ver := ProtoVer}}) ->
|
||||||
case pipeline([fun process_alias/2,
|
case pipeline([fun process_alias/2,
|
||||||
|
|
@ -426,7 +399,7 @@ handle_publish(Packet = ?PUBLISH_PACKET(_QoS, Topic, _PacketId),
|
||||||
{error, ReasonCode, NChannel} ->
|
{error, ReasonCode, NChannel} ->
|
||||||
?LOG(warning, "Cannot publish message to ~s due to ~s",
|
?LOG(warning, "Cannot publish message to ~s due to ~s",
|
||||||
[Topic, emqx_reason_codes:text(ReasonCode, ProtoVer)]),
|
[Topic, emqx_reason_codes:text(ReasonCode, ProtoVer)]),
|
||||||
handle_out({disconnect, ReasonCode}, NChannel)
|
handle_out(disconnect, ReasonCode, NChannel)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
process_publish(Packet = ?PUBLISH_PACKET(_QoS, _Topic, PacketId), Channel) ->
|
process_publish(Packet = ?PUBLISH_PACKET(_QoS, _Topic, PacketId), Channel) ->
|
||||||
|
|
@ -442,7 +415,7 @@ process_publish(PacketId, Msg = #message{qos = ?QOS_1}, Channel) ->
|
||||||
[] -> ?RC_NO_MATCHING_SUBSCRIBERS;
|
[] -> ?RC_NO_MATCHING_SUBSCRIBERS;
|
||||||
_ -> ?RC_SUCCESS
|
_ -> ?RC_SUCCESS
|
||||||
end,
|
end,
|
||||||
handle_out({puback, PacketId, ReasonCode}, Channel);
|
handle_out(puback, {PacketId, ReasonCode}, Channel);
|
||||||
|
|
||||||
process_publish(PacketId, Msg = #message{qos = ?QOS_2},
|
process_publish(PacketId, Msg = #message{qos = ?QOS_2},
|
||||||
Channel = #channel{session = Session}) ->
|
Channel = #channel{session = Session}) ->
|
||||||
|
|
@ -453,14 +426,14 @@ process_publish(PacketId, Msg = #message{qos = ?QOS_2},
|
||||||
_ -> ?RC_SUCCESS
|
_ -> ?RC_SUCCESS
|
||||||
end,
|
end,
|
||||||
NChannel = Channel#channel{session = NSession},
|
NChannel = Channel#channel{session = NSession},
|
||||||
handle_out({pubrec, PacketId, RC}, ensure_timer(await_timer, NChannel));
|
handle_out(pubrec, {PacketId, RC}, ensure_timer(await_timer, NChannel));
|
||||||
{error, RC = ?RC_PACKET_IDENTIFIER_IN_USE} ->
|
{error, RC = ?RC_PACKET_IDENTIFIER_IN_USE} ->
|
||||||
ok = emqx_metrics:inc('packets.publish.inuse'),
|
ok = emqx_metrics:inc('packets.publish.inuse'),
|
||||||
handle_out({pubrec, PacketId, RC}, Channel);
|
handle_out(pubrec, {PacketId, RC}, Channel);
|
||||||
{error, RC = ?RC_RECEIVE_MAXIMUM_EXCEEDED} ->
|
{error, RC = ?RC_RECEIVE_MAXIMUM_EXCEEDED} ->
|
||||||
?LOG(warning, "Dropped qos2 packet ~w due to awaiting_rel is full", [PacketId]),
|
?LOG(warning, "Dropped qos2 packet ~w due to awaiting_rel is full", [PacketId]),
|
||||||
ok = emqx_metrics:inc('messages.qos2.dropped'),
|
ok = emqx_metrics:inc('messages.qos2.dropped'),
|
||||||
handle_out({pubrec, PacketId, RC}, Channel)
|
handle_out(pubrec, {PacketId, RC}, Channel)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
publish_to_msg(Packet, #channel{conninfo = #{proto_ver := ProtoVer},
|
publish_to_msg(Packet, #channel{conninfo = #{proto_ver := ProtoVer},
|
||||||
|
|
@ -528,15 +501,11 @@ do_unsubscribe(TopicFilter, _SubOpts, Channel =
|
||||||
%% Handle outgoing packet
|
%% Handle outgoing packet
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-spec(handle_out(integer()|term(), channel())
|
-spec(handle_out(term(), channel())
|
||||||
-> {ok, channel()}
|
-> {ok, channel()}
|
||||||
| {ok, output(), channel()}
|
| {ok, output(), channel()}
|
||||||
| {stop, Reason :: term(), channel()}
|
| {shutdown, Reason :: term(), channel()}
|
||||||
| {stop, Reason :: term(), output(), channel()}).
|
| {shutdown, Reason :: term(), output(), channel()}).
|
||||||
handle_out(Bytes, Channel) when is_integer(Bytes) ->
|
|
||||||
NChannel = maybe_gc_and_check_oom(Bytes, Channel),
|
|
||||||
{ok, ensure_timer(stats_timer, NChannel)};
|
|
||||||
|
|
||||||
handle_out(Delivers, Channel = #channel{conn_state = disconnected,
|
handle_out(Delivers, Channel = #channel{conn_state = disconnected,
|
||||||
session = Session})
|
session = Session})
|
||||||
when is_list(Delivers) ->
|
when is_list(Delivers) ->
|
||||||
|
|
@ -557,7 +526,35 @@ handle_out(Delivers, Channel = #channel{session = Session}) when is_list(Deliver
|
||||||
{ok, Channel#channel{session = NSession}}
|
{ok, Channel#channel{session = NSession}}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_out({connack, ?RC_SUCCESS, SP, ConnPkt},
|
handle_out({publish, Publishes}, Channel) when is_list(Publishes) ->
|
||||||
|
Packets = lists:foldl(
|
||||||
|
fun(Publish, Acc) ->
|
||||||
|
case handle_out(Publish, Channel) of
|
||||||
|
{ok, Packet, _Ch} ->
|
||||||
|
[Packet|Acc];
|
||||||
|
{ok, _Ch} -> Acc
|
||||||
|
end
|
||||||
|
end, [], Publishes),
|
||||||
|
{ok, {outgoing, lists:reverse(Packets)}, Channel};
|
||||||
|
|
||||||
|
%% Ignore loop deliver
|
||||||
|
handle_out({publish, _PacketId, #message{from = ClientId,
|
||||||
|
flags = #{nl := true}}},
|
||||||
|
Channel = #channel{clientinfo = #{clientid := ClientId}}) ->
|
||||||
|
{ok, Channel};
|
||||||
|
|
||||||
|
handle_out({publish, PacketId, Msg}, Channel =
|
||||||
|
#channel{clientinfo = ClientInfo = #{mountpoint := MountPoint}}) ->
|
||||||
|
Msg1 = emqx_message:update_expiry(Msg),
|
||||||
|
Msg2 = emqx_hooks:run_fold('message.delivered', [ClientInfo], Msg1),
|
||||||
|
Msg3 = emqx_mountpoint:unmount(MountPoint, Msg2),
|
||||||
|
{ok, emqx_message:to_packet(PacketId, Msg3), Channel};
|
||||||
|
|
||||||
|
handle_out(Data, Channel) ->
|
||||||
|
?LOG(error, "Unexpected outgoing: ~p", [Data]),
|
||||||
|
{ok, Channel}.
|
||||||
|
|
||||||
|
handle_out(connack, {?RC_SUCCESS, SP, ConnPkt},
|
||||||
Channel = #channel{conninfo = ConnInfo,
|
Channel = #channel{conninfo = ConnInfo,
|
||||||
clientinfo = ClientInfo}) ->
|
clientinfo = ClientInfo}) ->
|
||||||
AckProps = run_fold([fun enrich_caps/2,
|
AckProps = run_fold([fun enrich_caps/2,
|
||||||
|
|
@ -574,16 +571,16 @@ handle_out({connack, ?RC_SUCCESS, SP, ConnPkt},
|
||||||
AckPacket = ?CONNACK_PACKET(?RC_SUCCESS, SP, AckProps),
|
AckPacket = ?CONNACK_PACKET(?RC_SUCCESS, SP, AckProps),
|
||||||
case maybe_resume_session(Channel2) of
|
case maybe_resume_session(Channel2) of
|
||||||
ignore ->
|
ignore ->
|
||||||
{ok, [{enter, connected}, {outgoing, AckPacket}], Channel2};
|
{ok, [{connack, AckPacket}], Channel2};
|
||||||
{ok, Publishes, NSession} ->
|
{ok, Publishes, NSession} ->
|
||||||
Channel3 = Channel2#channel{session = NSession,
|
Channel3 = Channel2#channel{session = NSession,
|
||||||
resuming = false,
|
resuming = false,
|
||||||
pendings = []},
|
pendings = []},
|
||||||
{ok, {outgoing, Packets}, _} = handle_out({publish, Publishes}, Channel3),
|
{ok, {outgoing, Packets}, _} = handle_out({publish, Publishes}, Channel3),
|
||||||
{ok, [{enter, connected}, {outgoing, [AckPacket|Packets]}], Channel3}
|
{ok, [{connack, AckPacket}, {outgoing, Packets}], Channel3}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_out({connack, ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnInfo,
|
handle_out(connack, {ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnInfo,
|
||||||
clientinfo = ClientInfo}) ->
|
clientinfo = ClientInfo}) ->
|
||||||
ok = emqx_hooks:run('client.connected', [ClientInfo, ReasonCode, ConnInfo]),
|
ok = emqx_hooks:run('client.connected', [ClientInfo, ReasonCode, ConnInfo]),
|
||||||
ReasonCode1 = case ProtoVer = maps:get(proto_ver, ConnInfo) of
|
ReasonCode1 = case ProtoVer = maps:get(proto_ver, ConnInfo) of
|
||||||
|
|
@ -593,65 +590,40 @@ handle_out({connack, ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnIn
|
||||||
Reason = emqx_reason_codes:name(ReasonCode1, ProtoVer),
|
Reason = emqx_reason_codes:name(ReasonCode1, ProtoVer),
|
||||||
shutdown(Reason, ?CONNACK_PACKET(ReasonCode1), Channel);
|
shutdown(Reason, ?CONNACK_PACKET(ReasonCode1), Channel);
|
||||||
|
|
||||||
handle_out({publish, Publishes}, Channel) when is_list(Publishes) ->
|
handle_out(puback, {PacketId, ReasonCode}, Channel) ->
|
||||||
Packets = lists:foldl(
|
{ok, ?PUBACK_PACKET(PacketId, ReasonCode), Channel};
|
||||||
fun(Publish, Acc) ->
|
|
||||||
case handle_out(Publish, Channel) of
|
|
||||||
{ok, Packet, _Ch} ->
|
|
||||||
[Packet|Acc];
|
|
||||||
{ok, _Ch} -> Acc
|
|
||||||
end
|
|
||||||
end, [], Publishes),
|
|
||||||
NChannel = inc_pub_stats(publish_out, length(Packets), Channel),
|
|
||||||
{ok, {outgoing, lists:reverse(Packets)}, NChannel};
|
|
||||||
|
|
||||||
%% Ignore loop deliver
|
handle_out(pubrec, {PacketId, ReasonCode}, Channel) ->
|
||||||
handle_out({publish, _PacketId, #message{from = ClientId,
|
{ok, ?PUBREC_PACKET(PacketId, ReasonCode), Channel};
|
||||||
flags = #{nl := true}}},
|
|
||||||
Channel = #channel{clientinfo = #{clientid := ClientId}}) ->
|
|
||||||
{ok, Channel};
|
|
||||||
|
|
||||||
handle_out({publish, PacketId, Msg}, Channel =
|
handle_out(pubrel, {PacketId, ReasonCode}, Channel) ->
|
||||||
#channel{clientinfo = ClientInfo = #{mountpoint := MountPoint}}) ->
|
{ok, ?PUBREL_PACKET(PacketId, ReasonCode), Channel};
|
||||||
Msg1 = emqx_message:update_expiry(Msg),
|
|
||||||
Msg2 = emqx_hooks:run_fold('message.delivered', [ClientInfo], Msg1),
|
|
||||||
Msg3 = emqx_mountpoint:unmount(MountPoint, Msg2),
|
|
||||||
{ok, emqx_message:to_packet(PacketId, Msg3), Channel};
|
|
||||||
|
|
||||||
handle_out({puback, PacketId, ReasonCode}, Channel) ->
|
handle_out(pubcomp, {PacketId, ReasonCode}, Channel) ->
|
||||||
{ok, ?PUBACK_PACKET(PacketId, ReasonCode), inc_pub_stats(puback_out, Channel)};
|
{ok, ?PUBCOMP_PACKET(PacketId, ReasonCode), Channel};
|
||||||
|
|
||||||
handle_out({pubrel, PacketId, ReasonCode}, Channel) ->
|
handle_out(suback, {PacketId, ReasonCodes},
|
||||||
{ok, ?PUBREL_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrel_out, Channel)};
|
|
||||||
|
|
||||||
handle_out({pubrec, PacketId, ReasonCode}, Channel) ->
|
|
||||||
{ok, ?PUBREC_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrec_out, Channel)};
|
|
||||||
|
|
||||||
handle_out({pubcomp, PacketId, ReasonCode}, Channel) ->
|
|
||||||
{ok, ?PUBCOMP_PACKET(PacketId, ReasonCode), inc_pub_stats(pubcomp_out, Channel)};
|
|
||||||
|
|
||||||
handle_out({suback, PacketId, ReasonCodes},
|
|
||||||
Channel = #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) ->
|
Channel = #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) ->
|
||||||
{ok, ?SUBACK_PACKET(PacketId, ReasonCodes), Channel};
|
{ok, ?SUBACK_PACKET(PacketId, ReasonCodes), Channel};
|
||||||
|
|
||||||
handle_out({suback, PacketId, ReasonCodes}, Channel) ->
|
handle_out(suback, {PacketId, ReasonCodes}, Channel) ->
|
||||||
ReasonCodes1 = [emqx_reason_codes:compat(suback, RC) || RC <- ReasonCodes],
|
ReasonCodes1 = [emqx_reason_codes:compat(suback, RC) || RC <- ReasonCodes],
|
||||||
{ok, ?SUBACK_PACKET(PacketId, ReasonCodes1), Channel};
|
{ok, ?SUBACK_PACKET(PacketId, ReasonCodes1), Channel};
|
||||||
|
|
||||||
handle_out({unsuback, PacketId, ReasonCodes},
|
handle_out(unsuback, {PacketId, ReasonCodes},
|
||||||
Channel = #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) ->
|
Channel = #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) ->
|
||||||
{ok, ?UNSUBACK_PACKET(PacketId, ReasonCodes), Channel};
|
{ok, ?UNSUBACK_PACKET(PacketId, ReasonCodes), Channel};
|
||||||
|
|
||||||
handle_out({unsuback, PacketId, _ReasonCodes}, Channel) ->
|
handle_out(unsuback, {PacketId, _ReasonCodes}, Channel) ->
|
||||||
{ok, ?UNSUBACK_PACKET(PacketId), Channel};
|
{ok, ?UNSUBACK_PACKET(PacketId), Channel};
|
||||||
|
|
||||||
handle_out({disconnect, ReasonCode}, Channel = #channel{conninfo = #{proto_ver := ProtoVer}}) ->
|
handle_out(disconnect, ReasonCode, Channel = #channel{conninfo = #{proto_ver := ProtoVer}})
|
||||||
|
when is_integer(ReasonCode) ->
|
||||||
ReasonName = emqx_reason_codes:name(ReasonCode, ProtoVer),
|
ReasonName = emqx_reason_codes:name(ReasonCode, ProtoVer),
|
||||||
handle_out({disconnect, ReasonCode, ReasonName}, Channel);
|
handle_out(disconnect, {ReasonCode, ReasonName}, Channel);
|
||||||
|
|
||||||
handle_out({disconnect, ReasonCode, ReasonName},
|
handle_out(disconnect, {ReasonCode, ReasonName}, Channel = #channel{conninfo = ConnInfo}) ->
|
||||||
Channel = #channel{conninfo = #{proto_ver := ProtoVer,
|
#{proto_ver := ProtoVer, expiry_interval := ExpiryInterval} = ConnInfo,
|
||||||
expiry_interval := ExpiryInterval}}) ->
|
|
||||||
case {ExpiryInterval, ProtoVer} of
|
case {ExpiryInterval, ProtoVer} of
|
||||||
{0, ?MQTT_PROTO_V5} ->
|
{0, ?MQTT_PROTO_V5} ->
|
||||||
shutdown(ReasonName, ?DISCONNECT_PACKET(ReasonCode), Channel);
|
shutdown(ReasonName, ?DISCONNECT_PACKET(ReasonCode), Channel);
|
||||||
|
|
@ -673,7 +645,7 @@ handle_out({disconnect, ReasonCode, ReasonName},
|
||||||
{ok, {close, ReasonName}, NChannel}
|
{ok, {close, ReasonName}, NChannel}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_out({Type, Data}, Channel) ->
|
handle_out(Type, Data, Channel) ->
|
||||||
?LOG(error, "Unexpected outgoing: ~s, ~p", [Type, Data]),
|
?LOG(error, "Unexpected outgoing: ~s, ~p", [Type, Data]),
|
||||||
{ok, Channel}.
|
{ok, Channel}.
|
||||||
|
|
||||||
|
|
@ -683,38 +655,43 @@ handle_out({Type, Data}, Channel) ->
|
||||||
|
|
||||||
-spec(handle_call(Req :: term(), channel())
|
-spec(handle_call(Req :: term(), channel())
|
||||||
-> {reply, Reply :: term(), channel()}
|
-> {reply, Reply :: term(), channel()}
|
||||||
| {stop, Reason :: term(), Reply :: term(), channel()}).
|
| {shutdown, Reason :: term(), Reply :: term(), channel()}).
|
||||||
handle_call(kick, Channel) ->
|
handle_call(kick, Channel) ->
|
||||||
{stop, {shutdown, kicked}, ok, Channel};
|
shutdown(kicked, ok, Channel);
|
||||||
|
|
||||||
handle_call(discard, Channel = #channel{conn_state = connected}) ->
|
handle_call(discard, Channel = #channel{conn_state = connected}) ->
|
||||||
Packet = ?DISCONNECT_PACKET(?RC_SESSION_TAKEN_OVER),
|
Packet = ?DISCONNECT_PACKET(?RC_SESSION_TAKEN_OVER),
|
||||||
{stop, {shutdown, discarded}, ok, Packet, Channel};
|
{shutdown, discarded, ok, Packet, Channel};
|
||||||
|
|
||||||
handle_call(discard, Channel = #channel{conn_state = disconnected}) ->
|
handle_call(discard, Channel = #channel{conn_state = disconnected}) ->
|
||||||
{stop, {shutdown, discarded}, ok, Channel};
|
shutdown(discarded, ok, Channel);
|
||||||
|
|
||||||
%% Session Takeover
|
%% Session Takeover
|
||||||
handle_call({takeover, 'begin'}, Channel = #channel{session = Session}) ->
|
handle_call({takeover, 'begin'}, Channel = #channel{session = Session}) ->
|
||||||
{reply, Session, Channel#channel{takeover = true}};
|
reply(Session, Channel#channel{takeover = true});
|
||||||
|
|
||||||
handle_call({takeover, 'end'}, Channel = #channel{session = Session,
|
handle_call({takeover, 'end'}, Channel = #channel{session = Session,
|
||||||
pendings = Pendings}) ->
|
pendings = Pendings}) ->
|
||||||
ok = emqx_session:takeover(Session),
|
ok = emqx_session:takeover(Session),
|
||||||
|
%% TODO: Should not drain deliver here
|
||||||
Delivers = emqx_misc:drain_deliver(),
|
Delivers = emqx_misc:drain_deliver(),
|
||||||
AllPendings = lists:append(Delivers, Pendings),
|
AllPendings = lists:append(Delivers, Pendings),
|
||||||
{stop, {shutdown, takeovered}, AllPendings, Channel};
|
shutdown(takeovered, AllPendings, Channel);
|
||||||
|
|
||||||
|
handle_call(list_acl_cache, Channel) ->
|
||||||
|
{reply, emqx_acl_cache:list_acl_cache(), Channel};
|
||||||
|
|
||||||
handle_call(Req, Channel) ->
|
handle_call(Req, Channel) ->
|
||||||
?LOG(error, "Unexpected call: ~p", [Req]),
|
?LOG(error, "Unexpected call: ~p", [Req]),
|
||||||
{reply, ignored, Channel}.
|
reply(ignored, Channel).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Handle Info
|
%% Handle Info
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-spec(handle_info(Info :: term(), channel())
|
-spec(handle_info(Info :: term(), channel())
|
||||||
-> ok | {ok, channel()} | {stop, Reason :: term(), channel()}).
|
-> ok | {ok, channel()}
|
||||||
|
| {shutdown, Reason :: term(), channel()}).
|
||||||
handle_info({subscribe, TopicFilters}, Channel = #channel{clientinfo = ClientInfo}) ->
|
handle_info({subscribe, TopicFilters}, Channel = #channel{clientinfo = ClientInfo}) ->
|
||||||
TopicFilters1 = emqx_hooks:run_fold('client.subscribe',
|
TopicFilters1 = emqx_hooks:run_fold('client.subscribe',
|
||||||
[ClientInfo, #{'Internal' => true}],
|
[ClientInfo, #{'Internal' => true}],
|
||||||
|
|
@ -729,11 +706,6 @@ handle_info({unsubscribe, TopicFilters}, Channel = #channel{clientinfo = ClientI
|
||||||
{_ReasonCodes, NChannel} = process_unsubscribe(TopicFilters1, Channel),
|
{_ReasonCodes, NChannel} = process_unsubscribe(TopicFilters1, Channel),
|
||||||
{ok, NChannel};
|
{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}) ->
|
handle_info({sock_closed, _Reason}, Channel = #channel{conn_state = disconnected}) ->
|
||||||
{ok, Channel};
|
{ok, Channel};
|
||||||
|
|
||||||
|
|
@ -757,6 +729,11 @@ handle_info({sock_closed, Reason}, Channel = #channel{conninfo = ConnInfo,
|
||||||
shutdown(Reason, Channel2)
|
shutdown(Reason, Channel2)
|
||||||
end;
|
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) ->
|
handle_info(Info, Channel) ->
|
||||||
?LOG(error, "Unexpected info: ~p~n", [Info]),
|
?LOG(error, "Unexpected info: ~p~n", [Info]),
|
||||||
error(unexpected_info),
|
error(unexpected_info),
|
||||||
|
|
@ -769,13 +746,7 @@ handle_info(Info, Channel) ->
|
||||||
-spec(handle_timeout(reference(), Msg :: term(), channel())
|
-spec(handle_timeout(reference(), Msg :: term(), channel())
|
||||||
-> {ok, channel()}
|
-> {ok, channel()}
|
||||||
| {ok, Result :: term(), channel()}
|
| {ok, Result :: term(), channel()}
|
||||||
| {stop, Reason :: 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},
|
handle_timeout(TRef, {keepalive, StatVal},
|
||||||
Channel = #channel{keepalive = Keepalive,
|
Channel = #channel{keepalive = Keepalive,
|
||||||
timers = #{alive_timer := TRef}}) ->
|
timers = #{alive_timer := TRef}}) ->
|
||||||
|
|
@ -784,7 +755,7 @@ handle_timeout(TRef, {keepalive, StatVal},
|
||||||
NChannel = Channel#channel{keepalive = NKeepalive},
|
NChannel = Channel#channel{keepalive = NKeepalive},
|
||||||
{ok, reset_timer(alive_timer, NChannel)};
|
{ok, reset_timer(alive_timer, NChannel)};
|
||||||
{error, timeout} ->
|
{error, timeout} ->
|
||||||
handle_out({disconnect, ?RC_KEEP_ALIVE_TIMEOUT}, Channel)
|
handle_out(disconnect, ?RC_KEEP_ALIVE_TIMEOUT, Channel)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_timeout(TRef, retry_delivery,
|
handle_timeout(TRef, retry_delivery,
|
||||||
|
|
@ -855,14 +826,12 @@ reset_timer(Name, Time, Channel) ->
|
||||||
clean_timer(Name, Channel = #channel{timers = Timers}) ->
|
clean_timer(Name, Channel = #channel{timers = Timers}) ->
|
||||||
Channel#channel{timers = maps:remove(Name, 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}) ->
|
interval(alive_timer, #channel{keepalive = KeepAlive}) ->
|
||||||
emqx_keepalive:info(interval, KeepAlive);
|
emqx_keepalive:info(interval, KeepAlive);
|
||||||
interval(retry_timer, #channel{session = Session}) ->
|
interval(retry_timer, #channel{session = Session}) ->
|
||||||
emqx_session:info(retry_interval, Session);
|
emqx_session:info(retry_interval, Session);
|
||||||
interval(await_timer, #channel{session = Session}) ->
|
interval(await_timer, #channel{session = Session}) ->
|
||||||
emqx_session:info(await_rel_timeout, Session);
|
emqx_session:info(awaiting_rel_timeout, Session);
|
||||||
interval(expire_timer, #channel{conninfo = ConnInfo}) ->
|
interval(expire_timer, #channel{conninfo = ConnInfo}) ->
|
||||||
timer:seconds(maps:get(expiry_interval, ConnInfo));
|
timer:seconds(maps:get(expiry_interval, ConnInfo));
|
||||||
interval(will_timer, #channel{will_msg = WillMsg}) ->
|
interval(will_timer, #channel{will_msg = WillMsg}) ->
|
||||||
|
|
@ -894,6 +863,7 @@ publish_will_msg(undefined) ->
|
||||||
publish_will_msg(Msg) ->
|
publish_will_msg(Msg) ->
|
||||||
emqx_broker:publish(Msg).
|
emqx_broker:publish(Msg).
|
||||||
|
|
||||||
|
|
||||||
%% @doc Enrich MQTT Connect Info.
|
%% @doc Enrich MQTT Connect Info.
|
||||||
enrich_conninfo(#mqtt_packet_connect{
|
enrich_conninfo(#mqtt_packet_connect{
|
||||||
proto_name = ProtoName,
|
proto_name = ProtoName,
|
||||||
|
|
@ -1050,13 +1020,13 @@ check_pub_acl(#mqtt_packet{variable = #mqtt_packet_publish{topic_name = Topic}},
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Check Pub Alias
|
%% Check Pub Alias
|
||||||
|
%% TODO: Fixme later
|
||||||
check_pub_alias(#mqtt_packet{
|
check_pub_alias(#mqtt_packet{
|
||||||
variable = #mqtt_packet_publish{
|
variable = #mqtt_packet_publish{
|
||||||
properties = #{'Topic-Alias' := AliasId}
|
properties = #{'Topic-Alias' := AliasId}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
#channel{alias_maximum = Limits}) ->
|
#channel{alias_maximum = Limits}) ->
|
||||||
%% TODO: Move to Protocol
|
|
||||||
case (Limits == undefined)
|
case (Limits == undefined)
|
||||||
orelse (Max = maps:get(inbound, Limits, 0)) == 0
|
orelse (Max = maps:get(inbound, Limits, 0)) == 0
|
||||||
orelse (AliasId > Max) of
|
orelse (AliasId > Max) of
|
||||||
|
|
@ -1104,6 +1074,7 @@ enrich_subopts(SubOpts, #channel{clientinfo = #{zone := Zone, is_bridge := IsBri
|
||||||
NL = flag(emqx_zone:ignore_loop_deliver(Zone)),
|
NL = flag(emqx_zone:ignore_loop_deliver(Zone)),
|
||||||
SubOpts#{rap => flag(IsBridge), nl => NL}.
|
SubOpts#{rap => flag(IsBridge), nl => NL}.
|
||||||
|
|
||||||
|
%% TODO: Default caps should not be returned.
|
||||||
enrich_caps(AckProps, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5},
|
enrich_caps(AckProps, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5},
|
||||||
clientinfo = #{zone := Zone}}) ->
|
clientinfo = #{zone := Zone}}) ->
|
||||||
#{max_packet_size := MaxPktSize,
|
#{max_packet_size := MaxPktSize,
|
||||||
|
|
@ -1152,7 +1123,7 @@ ensure_keepalive(_AckProps, Channel = #channel{conninfo = ConnInfo}) ->
|
||||||
|
|
||||||
ensure_keepalive_timer(0, Channel) -> Channel;
|
ensure_keepalive_timer(0, Channel) -> Channel;
|
||||||
ensure_keepalive_timer(Interval, Channel = #channel{clientinfo = #{zone := Zone}}) ->
|
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)),
|
Keepalive = emqx_keepalive:init(round(timer:seconds(Interval) * Backoff)),
|
||||||
ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}).
|
ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}).
|
||||||
|
|
||||||
|
|
@ -1178,32 +1149,29 @@ is_acl_enabled(#{zone := Zone, is_superuser := IsSuperuser}) ->
|
||||||
parse_topic_filters(TopicFilters) ->
|
parse_topic_filters(TopicFilters) ->
|
||||||
lists:map(fun emqx_topic:parse/1, 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
|
%% Helper functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-compile({inline, [reply/2]}).
|
||||||
|
reply(Reply, Channel) ->
|
||||||
|
{reply, Reply, Channel}.
|
||||||
|
|
||||||
|
-compile({inline, [shutdown/2]}).
|
||||||
|
shutdown(success, Channel) ->
|
||||||
|
shutdown(normal, Channel);
|
||||||
|
shutdown(Reason, Channel) ->
|
||||||
|
{shutdown, Reason, Channel}.
|
||||||
|
|
||||||
|
-compile({inline, [shutdown/3]}).
|
||||||
|
shutdown(success, Reply, Channel) ->
|
||||||
|
shutdown(normal, Reply, Channel);
|
||||||
|
shutdown(Reason, Reply, Channel) ->
|
||||||
|
{shutdown, Reason, Reply, Channel}.
|
||||||
|
|
||||||
sp(true) -> 1;
|
sp(true) -> 1;
|
||||||
sp(false) -> 0.
|
sp(false) -> 0.
|
||||||
|
|
||||||
flag(true) -> 1;
|
flag(true) -> 1;
|
||||||
flag(false) -> 0.
|
flag(false) -> 0.
|
||||||
|
|
||||||
shutdown(Reason, Channel) ->
|
|
||||||
{stop, {shutdown, Reason}, Channel}.
|
|
||||||
|
|
||||||
shutdown(Reason, Packets, Channel) ->
|
|
||||||
{stop, {shutdown, Reason}, Packets, Channel}.
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
%%--------------------------------------------------------------------
|
%%-------------------------------------------------------------------
|
||||||
%% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved.
|
%% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
%%
|
%%
|
||||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
@ -93,7 +93,6 @@ start_link() ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Register a channel.
|
%% @doc Register a channel.
|
||||||
%% Channel will be unregistered automatically when the channel process dies
|
|
||||||
-spec(register_channel(emqx_types:clientid()) -> ok).
|
-spec(register_channel(emqx_types:clientid()) -> ok).
|
||||||
register_channel(ClientId) when is_binary(ClientId) ->
|
register_channel(ClientId) when is_binary(ClientId) ->
|
||||||
register_channel(ClientId, self()).
|
register_channel(ClientId, self()).
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% MQTT/TCP Connection
|
%% MQTT/TCP|TLS Connection
|
||||||
-module(emqx_connection).
|
-module(emqx_connection).
|
||||||
|
|
||||||
-include("emqx.hrl").
|
-include("emqx.hrl").
|
||||||
|
|
@ -24,6 +24,11 @@
|
||||||
|
|
||||||
-logger_header("[MQTT]").
|
-logger_header("[MQTT]").
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
-endif.
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([ start_link/3
|
-export([ start_link/3
|
||||||
, stop/1
|
, stop/1
|
||||||
|
|
@ -35,7 +40,7 @@
|
||||||
|
|
||||||
-export([call/2]).
|
-export([call/2]).
|
||||||
|
|
||||||
%% callback
|
%% Callback
|
||||||
-export([init/4]).
|
-export([init/4]).
|
||||||
|
|
||||||
%% Sys callbacks
|
%% Sys callbacks
|
||||||
|
|
@ -45,12 +50,15 @@
|
||||||
, system_get_state/1
|
, system_get_state/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Internal callbacks
|
%% Internal callback
|
||||||
-export([wakeup_from_hib/2]).
|
-export([wakeup_from_hib/3]).
|
||||||
|
|
||||||
|
-import(emqx_misc,
|
||||||
|
[ maybe_apply/2
|
||||||
|
, start_timer/2
|
||||||
|
]).
|
||||||
|
|
||||||
-record(state, {
|
-record(state, {
|
||||||
%% Parent
|
|
||||||
parent :: pid(),
|
|
||||||
%% TCP/TLS Transport
|
%% TCP/TLS Transport
|
||||||
transport :: esockd:transport(),
|
transport :: esockd:transport(),
|
||||||
%% TCP/TLS Socket
|
%% TCP/TLS Socket
|
||||||
|
|
@ -59,34 +67,37 @@
|
||||||
peername :: emqx_types:peername(),
|
peername :: emqx_types:peername(),
|
||||||
%% Sockname of the connection
|
%% Sockname of the connection
|
||||||
sockname :: emqx_types:peername(),
|
sockname :: emqx_types:peername(),
|
||||||
%% Sock state
|
%% Sock State
|
||||||
sockstate :: emqx_types:sockstate(),
|
sockstate :: emqx_types:sockstate(),
|
||||||
%% The {active, N} option
|
%% The {active, N} option
|
||||||
active_n :: pos_integer(),
|
active_n :: pos_integer(),
|
||||||
%% Publish Limit
|
%% Limiter
|
||||||
pub_limit :: maybe(esockd_rate_limit:bucket()),
|
limiter :: maybe(emqx_limiter:limiter()),
|
||||||
%% Rate Limit
|
|
||||||
rate_limit :: maybe(esockd_rate_limit:bucket()),
|
|
||||||
%% Limit Timer
|
%% Limit Timer
|
||||||
limit_timer :: maybe(reference()),
|
limit_timer :: maybe(reference()),
|
||||||
%% Parser State
|
%% Parse State
|
||||||
parse_state :: emqx_frame:parse_state(),
|
parse_state :: emqx_frame:parse_state(),
|
||||||
%% Serialize function
|
%% Serialize function
|
||||||
serialize :: emqx_frame:serialize_fun(),
|
serialize :: emqx_frame:serialize_fun(),
|
||||||
%% Channel State
|
%% Channel State
|
||||||
channel :: emqx_channel:channel(),
|
channel :: emqx_channel:channel(),
|
||||||
%% Idle timer
|
%% GC State
|
||||||
idle_timer :: reference()
|
gc_state :: maybe(emqx_gc:gc_state()),
|
||||||
|
%% Stats Timer
|
||||||
|
stats_timer :: disabled | maybe(reference()),
|
||||||
|
%% Idle Timer
|
||||||
|
idle_timer :: maybe(reference())
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type(state() :: #state{}).
|
-type(state() :: #state{}).
|
||||||
|
|
||||||
-define(ACTIVE_N, 100).
|
-define(ACTIVE_N, 100).
|
||||||
-define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n,
|
-define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n, limiter]).
|
||||||
pub_limit, rate_limit]).
|
|
||||||
-define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]).
|
-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(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())
|
-spec(start_link(esockd:transport(), esockd:socket(), proplists:proplist())
|
||||||
-> {ok, pid()}).
|
-> {ok, pid()}).
|
||||||
start_link(Transport, Socket, Options) ->
|
start_link(Transport, Socket, Options) ->
|
||||||
|
|
@ -118,15 +129,8 @@ info(sockstate, #state{sockstate = SockSt}) ->
|
||||||
SockSt;
|
SockSt;
|
||||||
info(active_n, #state{active_n = ActiveN}) ->
|
info(active_n, #state{active_n = ActiveN}) ->
|
||||||
ActiveN;
|
ActiveN;
|
||||||
info(pub_limit, #state{pub_limit = PubLimit}) ->
|
info(limiter, #state{limiter = Limiter}) ->
|
||||||
limit_info(PubLimit);
|
maybe_apply(fun emqx_limiter:info/1, Limiter).
|
||||||
info(rate_limit, #state{rate_limit = RateLimit}) ->
|
|
||||||
limit_info(RateLimit);
|
|
||||||
info(channel, #state{channel = Channel}) ->
|
|
||||||
emqx_channel:info(Channel).
|
|
||||||
|
|
||||||
limit_info(Limit) ->
|
|
||||||
emqx_misc:maybe_apply(fun esockd_rate_limit:info/1, Limit).
|
|
||||||
|
|
||||||
%% @doc Get stats of the connection/channel.
|
%% @doc Get stats of the connection/channel.
|
||||||
-spec(stats(pid()|state()) -> emqx_types:stats()).
|
-spec(stats(pid()|state()) -> emqx_types:stats()).
|
||||||
|
|
@ -144,6 +148,13 @@ stats(#state{transport = Transport,
|
||||||
ProcStats = emqx_misc:proc_stats(),
|
ProcStats = emqx_misc:proc_stats(),
|
||||||
lists:append([SockStats, ConnStats, ChanStats, ProcStats]).
|
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) ->
|
call(Pid, Req) ->
|
||||||
gen_server:call(Pid, Req, infinity).
|
gen_server:call(Pid, Req, infinity).
|
||||||
|
|
||||||
|
|
@ -158,23 +169,14 @@ init(Parent, Transport, RawSocket, Options) ->
|
||||||
case Transport:wait(RawSocket) of
|
case Transport:wait(RawSocket) of
|
||||||
{ok, Socket} ->
|
{ok, Socket} ->
|
||||||
do_init(Parent, Transport, Socket, Options);
|
do_init(Parent, Transport, Socket, Options);
|
||||||
{error, Reason} when Reason =:= enotconn;
|
|
||||||
Reason =:= einval;
|
|
||||||
Reason =:= closed ->
|
|
||||||
Transport:fast_close(RawSocket),
|
|
||||||
exit(normal);
|
|
||||||
{error, timeout} ->
|
|
||||||
Transport:fast_close(RawSocket),
|
|
||||||
exit({shutdown, ssl_upgrade_timeout});
|
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
Transport:fast_close(RawSocket),
|
ok = Transport:fast_close(RawSocket),
|
||||||
exit(Reason)
|
exit_on_sock_error(Reason)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
do_init(Parent, Transport, Socket, Options) ->
|
do_init(Parent, Transport, Socket, Options) ->
|
||||||
{ok, Peername} = Transport:ensure_ok_or_exit(peername, [Socket]),
|
{ok, Peername} = Transport:ensure_ok_or_exit(peername, [Socket]),
|
||||||
{ok, Sockname} = Transport:ensure_ok_or_exit(sockname, [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]),
|
Peercert = Transport:ensure_ok_or_exit(peercert, [Socket]),
|
||||||
ConnInfo = #{socktype => Transport:type(Socket),
|
ConnInfo = #{socktype => Transport:type(Socket),
|
||||||
peername => Peername,
|
peername => Peername,
|
||||||
|
|
@ -184,52 +186,52 @@ do_init(Parent, Transport, Socket, Options) ->
|
||||||
},
|
},
|
||||||
Zone = proplists:get_value(zone, Options),
|
Zone = proplists:get_value(zone, Options),
|
||||||
ActiveN = proplists:get_value(active_n, Options, ?ACTIVE_N),
|
ActiveN = proplists:get_value(active_n, Options, ?ACTIVE_N),
|
||||||
PubLimit = init_limiter(emqx_zone:get_env(Zone, publish_limit)),
|
Limiter = emqx_limiter:init(Options),
|
||||||
RateLimit = init_limiter(proplists:get_value(rate_limit, Options)),
|
FrameOpts = emqx_zone:mqtt_frame_options(Zone),
|
||||||
FrameOpts = emqx_zone:frame_options(Zone),
|
|
||||||
ParseState = emqx_frame:initial_parse_state(FrameOpts),
|
ParseState = emqx_frame:initial_parse_state(FrameOpts),
|
||||||
Serialize = emqx_frame:serialize_fun(),
|
Serialize = emqx_frame:serialize_fun(),
|
||||||
Channel = emqx_channel:init(ConnInfo, Options),
|
Channel = emqx_channel:init(ConnInfo, Options),
|
||||||
IdleTimout = emqx_zone:get_env(Zone, idle_timeout, 30000),
|
GcState = emqx_zone:init_gc_state(Zone),
|
||||||
IdleTimer = emqx_misc:start_timer(IdleTimout, idle_timeout),
|
StatsTimer = emqx_zone:stats_timer(Zone),
|
||||||
HibAfterTimeout = emqx_zone:get_env(Zone, hibernate_after, IdleTimout*2),
|
IdleTimeout = emqx_zone:idle_timeout(Zone),
|
||||||
State = #state{parent = Parent,
|
IdleTimer = start_timer(IdleTimeout, idle_timeout),
|
||||||
transport = Transport,
|
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,
|
socket = Socket,
|
||||||
peername = Peername,
|
peername = Peername,
|
||||||
sockname = Sockname,
|
sockname = Sockname,
|
||||||
sockstate = idle,
|
sockstate = idle,
|
||||||
active_n = ActiveN,
|
active_n = ActiveN,
|
||||||
pub_limit = PubLimit,
|
limiter = Limiter,
|
||||||
rate_limit = RateLimit,
|
|
||||||
parse_state = ParseState,
|
parse_state = ParseState,
|
||||||
serialize = Serialize,
|
serialize = Serialize,
|
||||||
channel = Channel,
|
channel = Channel,
|
||||||
|
gc_state = GcState,
|
||||||
|
stats_timer = StatsTimer,
|
||||||
idle_timer = IdleTimer
|
idle_timer = IdleTimer
|
||||||
},
|
},
|
||||||
case activate_socket(State) of
|
case activate_socket(State) of
|
||||||
{ok, NState} ->
|
{ok, NState} ->
|
||||||
recvloop(NState, #{hibernate_after => HibAfterTimeout});
|
hibernate(Parent, NState, #{idle_timeout => IdleTimeout});
|
||||||
{error, Reason} when Reason =:= einval;
|
|
||||||
Reason =:= enotconn;
|
|
||||||
Reason =:= closed ->
|
|
||||||
Transport:fast_close(Socket),
|
|
||||||
exit(normal);
|
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
Transport:fast_close(Socket),
|
ok = Transport:fast_close(Socket),
|
||||||
erlang:exit({shutdown, Reason})
|
exit_on_sock_error(Reason)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-compile({inline, [init_limiter/1]}).
|
exit_on_sock_error(Reason) when Reason =:= einval;
|
||||||
init_limiter(undefined) -> undefined;
|
Reason =:= enotconn;
|
||||||
init_limiter({Rate, Burst}) ->
|
Reason =:= closed ->
|
||||||
esockd_rate_limit:new(Rate, Burst).
|
erlang:exit(normal);
|
||||||
|
exit_on_sock_error(timeout) ->
|
||||||
|
erlang:exit({shutdown, ssl_upgrade_timeout});
|
||||||
|
exit_on_sock_error(Reason) ->
|
||||||
|
erlang:exit({shutdown, Reason}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Recv Loop
|
%% Recv Loop
|
||||||
|
|
||||||
recvloop(State = #state{parent = Parent},
|
recvloop(Parent, State, Options = #{idle_timeout := IdleTimeout}) ->
|
||||||
Options = #{hibernate_after := HibAfterTimeout}) ->
|
|
||||||
receive
|
receive
|
||||||
{system, From, Request} ->
|
{system, From, Request} ->
|
||||||
sys:handle_system_msg(Request, From, Parent,
|
sys:handle_system_msg(Request, From, Parent,
|
||||||
|
|
@ -237,33 +239,49 @@ recvloop(State = #state{parent = Parent},
|
||||||
{'EXIT', Parent, Reason} ->
|
{'EXIT', Parent, Reason} ->
|
||||||
terminate(Reason, State);
|
terminate(Reason, State);
|
||||||
Msg ->
|
Msg ->
|
||||||
process_msg([Msg], State, Options)
|
NState = ensure_stats_timer(IdleTimeout, State),
|
||||||
|
process_msg([Msg], Parent, NState, Options)
|
||||||
after
|
after
|
||||||
HibAfterTimeout ->
|
IdleTimeout ->
|
||||||
hibernate(State, Options)
|
NState = cancel_stats_timer(State),
|
||||||
|
hibernate(Parent, NState, Options)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
hibernate(State, Options) ->
|
hibernate(Parent, State, Options) ->
|
||||||
proc_lib:hibernate(?MODULE, wakeup_from_hib, [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.
|
%% 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 next Msg
|
||||||
|
|
||||||
process_msg([], State, Options) ->
|
process_msg([], Parent, State, Options) ->
|
||||||
recvloop(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
|
case catch handle_msg(Msg, State) of
|
||||||
ok ->
|
ok ->
|
||||||
process_msg(More, State, Options);
|
process_msg(More, Parent, State, Options);
|
||||||
{ok, NState} ->
|
{ok, NState} ->
|
||||||
process_msg(More, NState, Options);
|
process_msg(More, Parent, NState, Options);
|
||||||
{ok, NextMsgs, NState} ->
|
{ok, Msgs, NState} ->
|
||||||
process_msg(append_msg(NextMsgs, More), NState, Options);
|
process_msg(append_msg(Msgs, More), Parent, NState, Options);
|
||||||
{stop, Reason} ->
|
{stop, Reason} ->
|
||||||
terminate(Reason, State);
|
terminate(Reason, State);
|
||||||
{stop, Reason, NState} ->
|
{stop, Reason, NState} ->
|
||||||
|
|
@ -285,14 +303,12 @@ handle_msg({'$gen_call', From, Req}, State) ->
|
||||||
stop(Reason, NState)
|
stop(Reason, NState)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_msg({Inet, _Sock, Data}, State = #state{channel = Channel})
|
handle_msg({Inet, _Sock, Data}, State) when Inet == tcp; Inet == ssl ->
|
||||||
when Inet == tcp; Inet == ssl ->
|
|
||||||
?LOG(debug, "RECV ~p", [Data]),
|
?LOG(debug, "RECV ~p", [Data]),
|
||||||
Oct = iolist_size(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),
|
ok = emqx_metrics:inc('bytes.received', Oct),
|
||||||
{ok, NChannel} = emqx_channel:handle_in(Oct, Channel),
|
parse_incoming(Data, State);
|
||||||
process_incoming(Data, State#state{channel = NChannel});
|
|
||||||
|
|
||||||
handle_msg({incoming, Packet = ?CONNECT_PACKET(ConnPkt)},
|
handle_msg({incoming, Packet = ?CONNECT_PACKET(ConnPkt)},
|
||||||
State = #state{idle_timer = IdleTimer}) ->
|
State = #state{idle_timer = IdleTimer}) ->
|
||||||
|
|
@ -303,6 +319,9 @@ handle_msg({incoming, Packet = ?CONNECT_PACKET(ConnPkt)},
|
||||||
},
|
},
|
||||||
handle_incoming(Packet, NState);
|
handle_incoming(Packet, NState);
|
||||||
|
|
||||||
|
handle_msg({incoming, ?PACKET(?PINGREQ)}, State) ->
|
||||||
|
handle_outgoing(?PACKET(?PINGRESP), State);
|
||||||
|
|
||||||
handle_msg({incoming, Packet}, State) ->
|
handle_msg({incoming, Packet}, State) ->
|
||||||
handle_incoming(Packet, State);
|
handle_incoming(Packet, State);
|
||||||
|
|
||||||
|
|
@ -316,37 +335,34 @@ handle_msg({Closed, _Sock}, State)
|
||||||
|
|
||||||
handle_msg({Passive, _Sock}, State)
|
handle_msg({Passive, _Sock}, State)
|
||||||
when Passive == tcp_passive; Passive == ssl_passive ->
|
when Passive == tcp_passive; Passive == ssl_passive ->
|
||||||
%% Rate limit and activate socket here.
|
InStats = #{cnt => emqx_pd:reset_counter(incoming_pubs),
|
||||||
NState = ensure_rate_limit(State),
|
oct => emqx_pd:reset_counter(incoming_bytes)
|
||||||
case activate_socket(NState) of
|
|
||||||
{ok, NState} -> {ok, NState};
|
|
||||||
{error, Reason} ->
|
|
||||||
{ok, {sock_error, Reason}, NState}
|
|
||||||
end;
|
|
||||||
|
|
||||||
%% Rate limit timer expired.
|
|
||||||
handle_msg(activate_socket, State) ->
|
|
||||||
NState = State#state{sockstate = idle,
|
|
||||||
limit_timer = undefined
|
|
||||||
},
|
},
|
||||||
case activate_socket(NState) of
|
%% Ensure Rate Limit
|
||||||
{ok, NState} -> {ok, NState};
|
NState = ensure_rate_limit(InStats, State),
|
||||||
{error, Reason} ->
|
%% Run GC and Check OOM
|
||||||
{ok, {sock_error, Reason}, State}
|
NState1 = check_oom(run_gc(InStats, NState)),
|
||||||
end;
|
handle_info(activate_socket, NState1);
|
||||||
|
|
||||||
handle_msg(Deliver = {deliver, _Topic, _Msg},
|
handle_msg(Deliver = {deliver, _Topic, _Msg},
|
||||||
State = #state{channel = Channel}) ->
|
State = #state{channel = Channel}) ->
|
||||||
Delivers = emqx_misc:drain_deliver([Deliver]),
|
Delivers = [Deliver|emqx_misc:drain_deliver()],
|
||||||
Result = emqx_channel:handle_out(Delivers, Channel),
|
Ret = emqx_channel:handle_out(Delivers, Channel),
|
||||||
handle_return(Result, State);
|
handle_chan_return(Ret, State);
|
||||||
|
|
||||||
handle_msg({outgoing, Packets}, State) ->
|
handle_msg({outgoing, Packets}, State) ->
|
||||||
{ok, handle_outgoing(Packets, State)};
|
handle_outgoing(Packets, State);
|
||||||
|
|
||||||
%% something sent
|
%% Something sent
|
||||||
handle_msg({inet_reply, _Sock, ok}, _State) ->
|
handle_msg({inet_reply, _Sock, ok}, State = #state{active_n = ActiveN}) ->
|
||||||
ok;
|
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_msg({inet_reply, _Sock, {error, Reason}}, State) ->
|
||||||
handle_info({sock_error, Reason}, State);
|
handle_info({sock_error, Reason}, State);
|
||||||
|
|
@ -357,25 +373,23 @@ handle_msg({timeout, TRef, TMsg}, State) ->
|
||||||
handle_msg(Shutdown = {shutdown, _Reason}, State) ->
|
handle_msg(Shutdown = {shutdown, _Reason}, State) ->
|
||||||
stop(Shutdown, State);
|
stop(Shutdown, State);
|
||||||
|
|
||||||
handle_msg(Msg, State) -> handle_info(Msg, State).
|
handle_msg(Msg, State) ->
|
||||||
|
handle_info(Msg, State).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Terminate
|
%% Terminate
|
||||||
|
|
||||||
terminate(Reason, #state{transport = Transport,
|
terminate(Reason, State = #state{channel = Channel}) ->
|
||||||
socket = Socket,
|
?LOG(debug, "Terminated due to ~p", [Reason]),
|
||||||
sockstate = SockSt,
|
|
||||||
channel = Channel}) ->
|
|
||||||
?LOG(debug, "Terminated for ~p", [Reason]),
|
|
||||||
SockSt =:= closed orelse Transport:fast_close(Socket),
|
|
||||||
emqx_channel:terminate(Reason, Channel),
|
emqx_channel:terminate(Reason, Channel),
|
||||||
|
close_socket(State),
|
||||||
exit(Reason).
|
exit(Reason).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Sys callbacks
|
%% Sys callbacks
|
||||||
|
|
||||||
system_continue(_Parent, _Deb, {State, Options}) ->
|
system_continue(Parent, _Deb, {State, Options}) ->
|
||||||
recvloop(State, Options).
|
recvloop(Parent, State, Options).
|
||||||
|
|
||||||
system_terminate(Reason, _Parent, _Deb, {State, _}) ->
|
system_terminate(Reason, _Parent, _Deb, {State, _}) ->
|
||||||
terminate(Reason, State).
|
terminate(Reason, State).
|
||||||
|
|
@ -399,22 +413,32 @@ handle_call(_From, Req, State = #state{channel = Channel}) ->
|
||||||
case emqx_channel:handle_call(Req, Channel) of
|
case emqx_channel:handle_call(Req, Channel) of
|
||||||
{reply, Reply, NChannel} ->
|
{reply, Reply, NChannel} ->
|
||||||
{reply, Reply, State#state{channel = NChannel}};
|
{reply, Reply, State#state{channel = NChannel}};
|
||||||
{stop, Reason, Reply, NChannel} ->
|
{shutdown, Reason, Reply, NChannel} ->
|
||||||
{stop, Reason, Reply, State#state{channel = NChannel}};
|
shutdown(Reason, Reply, State#state{channel = NChannel});
|
||||||
{stop, Reason, Reply, OutPacket, NChannel} ->
|
{shutdown, Reason, Reply, OutPacket, NChannel} ->
|
||||||
NState = State#state{channel = NChannel},
|
NState = State#state{channel = NChannel},
|
||||||
NState1 = handle_outgoing(OutPacket, NState),
|
ok = handle_outgoing(OutPacket, NState),
|
||||||
{stop, Reason, Reply, NState1}
|
shutdown(Reason, Reply, NState)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Handle timeout
|
%% Handle timeout
|
||||||
|
|
||||||
handle_timeout(TRef, idle_timeout, State = #state{idle_timer = TRef}) ->
|
handle_timeout(TRef, idle_timeout, State = #state{idle_timer = TRef}) ->
|
||||||
stop(idle_timeout, State);
|
shutdown(idle_timeout, State);
|
||||||
|
|
||||||
handle_timeout(TRef, emit_stats, State) ->
|
handle_timeout(TRef, limit_timeout, State = #state{limit_timer = TRef}) ->
|
||||||
handle_timeout(TRef, {emit_stats, stats(State)}, State);
|
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,
|
handle_timeout(TRef, keepalive, State = #state{transport = Transport,
|
||||||
socket = Socket}) ->
|
socket = Socket}) ->
|
||||||
|
|
@ -422,23 +446,20 @@ handle_timeout(TRef, keepalive, State = #state{transport = Transport,
|
||||||
{ok, [{recv_oct, RecvOct}]} ->
|
{ok, [{recv_oct, RecvOct}]} ->
|
||||||
handle_timeout(TRef, {keepalive, RecvOct}, State);
|
handle_timeout(TRef, {keepalive, RecvOct}, State);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
handle_info({sockerr, Reason}, State)
|
handle_info({sock_error, Reason}, State)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle_timeout(TRef, Msg, State = #state{channel = Channel}) ->
|
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).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Process/Parse incoming data.
|
%% Parse incoming data
|
||||||
|
|
||||||
-compile({inline, [process_incoming/2]}).
|
|
||||||
process_incoming(Data, State) ->
|
|
||||||
{Packets, NState} = parse_incoming(Data, State),
|
|
||||||
{ok, next_incoming_msgs(Packets), NState}.
|
|
||||||
|
|
||||||
-compile({inline, [parse_incoming/2]}).
|
-compile({inline, [parse_incoming/2]}).
|
||||||
parse_incoming(Data, State) ->
|
parse_incoming(Data, State) ->
|
||||||
parse_incoming(Data, [], State).
|
{Packets, NState} = parse_incoming(Data, [], State),
|
||||||
|
{ok, next_incoming_msgs(Packets), NState}.
|
||||||
|
|
||||||
parse_incoming(<<>>, Packets, State) ->
|
parse_incoming(<<>>, Packets, State) ->
|
||||||
{Packets, State};
|
{Packets, State};
|
||||||
|
|
@ -465,30 +486,30 @@ next_incoming_msgs(Packets) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Handle incoming packet
|
%% Handle incoming packet
|
||||||
|
|
||||||
handle_incoming(Packet = ?PACKET(Type), State = #state{channel = Channel}) ->
|
handle_incoming(Packet, State = #state{channel = Channel})
|
||||||
_ = inc_incoming_stats(Type),
|
when is_record(Packet, mqtt_packet) ->
|
||||||
ok = emqx_metrics:inc_recv(Packet),
|
ok = inc_incoming_stats(Packet),
|
||||||
?LOG(debug, "RECV ~s", [emqx_packet:format(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_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 channel return
|
||||||
|
|
||||||
handle_return(ok, State) ->
|
handle_chan_return(ok, State) ->
|
||||||
{ok, State};
|
{ok, State};
|
||||||
handle_return({ok, NChannel}, State) ->
|
handle_chan_return({ok, NChannel}, State) ->
|
||||||
{ok, State#state{channel = NChannel}};
|
{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}};
|
{ok, next_msgs(Replies), State#state{channel = NChannel}};
|
||||||
handle_return({stop, Reason, NChannel}, State) ->
|
handle_chan_return({shutdown, Reason, NChannel}, State) ->
|
||||||
stop(Reason, State#state{channel = NChannel});
|
shutdown(Reason, State#state{channel = NChannel});
|
||||||
handle_return({stop, Reason, OutPacket, NChannel}, State) ->
|
handle_chan_return({shutdown, Reason, OutPacket, NChannel}, State) ->
|
||||||
NState = State#state{channel = NChannel},
|
NState = State#state{channel = NChannel},
|
||||||
NState1 = handle_outgoing(OutPacket, NState),
|
ok = handle_outgoing(OutPacket, NState),
|
||||||
stop(Reason, NState1).
|
shutdown(Reason, NState).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Handle outgoing packets
|
%% Handle outgoing packets
|
||||||
|
|
@ -500,14 +521,13 @@ handle_outgoing(Packet, State) ->
|
||||||
send((serialize_and_inc_stats_fun(State))(Packet), State).
|
send((serialize_and_inc_stats_fun(State))(Packet), State).
|
||||||
|
|
||||||
serialize_and_inc_stats_fun(#state{serialize = Serialize}) ->
|
serialize_and_inc_stats_fun(#state{serialize = Serialize}) ->
|
||||||
fun(Packet = ?PACKET(Type)) ->
|
fun(Packet) ->
|
||||||
case Serialize(Packet) of
|
case Serialize(Packet) of
|
||||||
<<>> -> ?LOG(warning, "~s is discarded due to the frame is too large!",
|
<<>> -> ?LOG(warning, "~s is discarded due to the frame is too large!",
|
||||||
[emqx_packet:format(Packet)]),
|
[emqx_packet:format(Packet)]),
|
||||||
<<>>;
|
<<>>;
|
||||||
Data -> _ = inc_outgoing_stats(Type),
|
Data -> ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]),
|
||||||
_ = emqx_metrics:inc_sent(Packet),
|
ok = inc_outgoing_stats(Packet),
|
||||||
?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]),
|
|
||||||
Data
|
Data
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
@ -515,35 +535,53 @@ serialize_and_inc_stats_fun(#state{serialize = Serialize}) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Send data
|
%% Send data
|
||||||
|
|
||||||
send(IoData, State = #state{transport = Transport,
|
-spec(send(iodata(), state()) -> ok).
|
||||||
socket = Socket,
|
send(IoData, #state{transport = Transport, socket = Socket}) ->
|
||||||
channel = Channel}) ->
|
|
||||||
Oct = iolist_size(IoData),
|
Oct = iolist_size(IoData),
|
||||||
ok = emqx_metrics:inc('bytes.sent', Oct),
|
ok = emqx_metrics:inc('bytes.sent', Oct),
|
||||||
|
emqx_pd:inc_counter(outgoing_bytes, Oct),
|
||||||
case Transport:async_send(Socket, IoData) of
|
case Transport:async_send(Socket, IoData) of
|
||||||
ok ->
|
ok -> ok;
|
||||||
{ok, NChannel} = emqx_channel:handle_out(Oct, Channel),
|
|
||||||
State#state{channel = NChannel};
|
|
||||||
Error = {error, _Reason} ->
|
Error = {error, _Reason} ->
|
||||||
%% Simulate an inet_reply to postpone handling the error
|
%% Send an inet_reply to postpone handling the error
|
||||||
self() ! {inet_reply, Socket, Error}, State
|
self() ! {inet_reply, Socket, Error},
|
||||||
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Handle Info
|
%% Handle Info
|
||||||
|
|
||||||
handle_info({enter, _}, State = #state{active_n = ActiveN,
|
handle_info({connack, ConnAck}, State = #state{channel = Channel}) ->
|
||||||
sockstate = SockSt,
|
#{clientid := ClientId} = emqx_channel:info(clientinfo, Channel),
|
||||||
channel = Channel}) ->
|
ok = emqx_cm:register_channel(ClientId),
|
||||||
ChanAttrs = emqx_channel:attrs(Channel),
|
ok = emqx_cm:set_chan_attrs(ClientId, attrs(State)),
|
||||||
SockAttrs = #{active_n => ActiveN,
|
ok = emqx_cm:set_chan_stats(ClientId, stats(State)),
|
||||||
sockstate => SockSt
|
ok = handle_outgoing(ConnAck, State);
|
||||||
},
|
|
||||||
Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}),
|
|
||||||
handle_info({register, Attrs, stats(State)}, State);
|
|
||||||
|
|
||||||
handle_info({sockerr, _Reason}, #state{sockstate = closed}) -> ok;
|
handle_info({enter, disconnected}, State = #state{channel = Channel}) ->
|
||||||
handle_info({sockerr, Reason}, State) ->
|
#{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 = #state{sockstate = OldSst}) ->
|
||||||
|
case activate_socket(State) of
|
||||||
|
{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) ->
|
||||||
?LOG(debug, "Socket error: ~p", [Reason]),
|
?LOG(debug, "Socket error: ~p", [Reason]),
|
||||||
handle_info({sock_closed, Reason}, close_socket(State));
|
handle_info({sock_closed, Reason}, close_socket(State));
|
||||||
|
|
||||||
|
|
@ -557,7 +595,45 @@ handle_info({close, Reason}, State) ->
|
||||||
handle_info({sock_closed, Reason}, close_socket(State));
|
handle_info({sock_closed, Reason}, close_socket(State));
|
||||||
|
|
||||||
handle_info(Info, State = #state{channel = Channel}) ->
|
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
|
%% Activate Socket
|
||||||
|
|
@ -578,52 +654,36 @@ activate_socket(State = #state{transport = Transport,
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Close Socket
|
%% Close Socket
|
||||||
|
|
||||||
|
close_socket(State = #state{sockstate = closed}) ->
|
||||||
|
State;
|
||||||
close_socket(State = #state{transport = Transport, socket = Socket}) ->
|
close_socket(State = #state{transport = Transport, socket = Socket}) ->
|
||||||
ok = Transport:fast_close(Socket),
|
ok = Transport:fast_close(Socket),
|
||||||
State#state{sockstate = closed}.
|
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
|
%% Inc incoming/outgoing stats
|
||||||
|
|
||||||
-compile({inline, [inc_incoming_stats/1]}).
|
-compile({inline, [inc_incoming_stats/1]}).
|
||||||
inc_incoming_stats(Type) when is_integer(Type) ->
|
inc_incoming_stats(Packet = ?PACKET(Type)) ->
|
||||||
emqx_pd:update_counter(recv_pkt, 1),
|
emqx_pd:inc_counter(recv_pkt, 1),
|
||||||
if
|
if
|
||||||
Type == ?PUBLISH ->
|
Type == ?PUBLISH ->
|
||||||
emqx_pd:update_counter(recv_msg, 1),
|
emqx_pd:inc_counter(recv_msg, 1),
|
||||||
emqx_pd:update_counter(incoming_pubs, 1);
|
emqx_pd:inc_counter(incoming_pubs, 1);
|
||||||
true -> ok
|
true -> ok
|
||||||
end.
|
end,
|
||||||
|
emqx_metrics:inc_recv(Packet).
|
||||||
|
|
||||||
-compile({inline, [inc_outgoing_stats/1]}).
|
-compile({inline, [inc_outgoing_stats/1]}).
|
||||||
inc_outgoing_stats(Type) ->
|
inc_outgoing_stats(Packet = ?PACKET(Type)) ->
|
||||||
emqx_pd:update_counter(send_pkt, 1),
|
emqx_pd:inc_counter(send_pkt, 1),
|
||||||
(Type == ?PUBLISH) andalso emqx_pd:update_counter(send_msg, 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
|
%% Helper functions
|
||||||
|
|
@ -641,7 +701,17 @@ next_msgs(Action) when is_tuple(Action) ->
|
||||||
next_msgs(Actions) when is_list(Actions) ->
|
next_msgs(Actions) when is_list(Actions) ->
|
||||||
Actions.
|
Actions.
|
||||||
|
|
||||||
-compile({inline, [stop/2]}).
|
-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, stop/3]}).
|
||||||
stop(Reason, State) ->
|
stop(Reason, State) ->
|
||||||
{stop, Reason, State}.
|
{stop, Reason, State}.
|
||||||
|
|
||||||
|
stop(Reason, Reply, State) ->
|
||||||
|
{stop, Reason, Reply, State}.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ detect(#{clientid := ClientId, peerhost := PeerHost},
|
||||||
%% Create a flapping record.
|
%% Create a flapping record.
|
||||||
Flapping = #flapping{clientid = ClientId,
|
Flapping = #flapping{clientid = ClientId,
|
||||||
peerhost = PeerHost,
|
peerhost = PeerHost,
|
||||||
started_at = emqx_time:now_ms(),
|
started_at = erlang:system_time(millisecond),
|
||||||
detect_cnt = 1
|
detect_cnt = 1
|
||||||
},
|
},
|
||||||
true = ets:insert(?FLAPPING_TAB, Flapping),
|
true = ets:insert(?FLAPPING_TAB, Flapping),
|
||||||
|
|
@ -111,7 +111,7 @@ detect(#{clientid := ClientId, peerhost := PeerHost},
|
||||||
get_policy() ->
|
get_policy() ->
|
||||||
emqx:get_env(flapping_detect_policy, ?DEFAULT_DETECT_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
|
%% gen_server callbacks
|
||||||
|
|
@ -143,15 +143,15 @@ handle_cast({detected, Flapping = #flapping{clientid = ClientId,
|
||||||
[ClientId, esockd_net:ntoa(PeerHost), DetectCnt, Duration]),
|
[ClientId, esockd_net:ntoa(PeerHost), DetectCnt, Duration]),
|
||||||
%% Banned.
|
%% Banned.
|
||||||
BannedFlapping = Flapping#flapping{clientid = {banned, ClientId},
|
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}),
|
alarm_handler:set_alarm({{flapping_detected, ClientId}, BannedFlapping}),
|
||||||
ets:insert(?FLAPPING_TAB, BannedFlapping);
|
ets:insert(?FLAPPING_TAB, BannedFlapping);
|
||||||
false ->
|
false ->
|
||||||
?LOG(warning, "~s(~s) disconnected ~w times in ~wms",
|
?LOG(warning, "~s(~s) disconnected ~w times in ~wms",
|
||||||
[ClientId, esockd_net:ntoa(PeerHost), DetectCnt, Interval]),
|
[ClientId, esockd_net:ntoa(PeerHost), DetectCnt, Interval])
|
||||||
ets:delete_object(?FLAPPING_TAB, Flapping)
|
|
||||||
end,
|
end,
|
||||||
|
ets:delete_object(?FLAPPING_TAB, Flapping),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
handle_cast(Msg, State) ->
|
handle_cast(Msg, State) ->
|
||||||
|
|
@ -160,7 +160,8 @@ handle_cast(Msg, State) ->
|
||||||
|
|
||||||
handle_info({timeout, TRef, expire_flapping}, State = #{tref := TRef}) ->
|
handle_info({timeout, TRef, expire_flapping}, State = #{tref := TRef}) ->
|
||||||
with_flapping_tab(fun expire_flapping/2,
|
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};
|
{noreply, ensure_timer(State#{tref => undefined}), hibernate};
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
handle_info(Info, State) ->
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
-include("types.hrl").
|
-include("types.hrl").
|
||||||
|
|
||||||
-export([ init/1
|
-export([ init/1
|
||||||
|
, run/2
|
||||||
, run/3
|
, run/3
|
||||||
, info/1
|
, info/1
|
||||||
, reset/1
|
, reset/1
|
||||||
|
|
@ -57,21 +58,26 @@ init(#{count := Count, bytes := Bytes}) ->
|
||||||
?GCS(maps:from_list(Cnt ++ Oct)).
|
?GCS(maps:from_list(Cnt ++ Oct)).
|
||||||
|
|
||||||
%% @doc Try to run GC based on reduntions of count or bytes.
|
%% @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())
|
-spec(run(pos_integer(), pos_integer(), gc_state())
|
||||||
-> {boolean(), gc_state()}).
|
-> {boolean(), gc_state()}).
|
||||||
run(Cnt, Oct, ?GCS(St)) ->
|
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)}.
|
{Res, ?GCS(St1)}.
|
||||||
|
|
||||||
run([], St) ->
|
do_run([], St) ->
|
||||||
{false, St};
|
{false, St};
|
||||||
run([{K, N}|T], St) ->
|
do_run([{K, N}|T], St) ->
|
||||||
case dec(K, N, St) of
|
case dec(K, N, St) of
|
||||||
{true, St1} ->
|
{true, St1} ->
|
||||||
true = erlang:garbage_collect(),
|
erlang:garbage_collect(),
|
||||||
{true, do_reset(St1)};
|
{true, do_reset(St1)};
|
||||||
{false, St1} ->
|
{false, St1} ->
|
||||||
run(T, St1)
|
do_run(T, St1)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Info of GC state.
|
%% @doc Info of GC state.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
, set_primary_log_level/1
|
, set_primary_log_level/1
|
||||||
, set_log_handler_level/2
|
, set_log_handler_level/2
|
||||||
, set_log_level/1
|
, set_log_level/1
|
||||||
|
, set_all_log_handlers_level/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ get_primary_log_level/0
|
-export([ get_primary_log_level/0
|
||||||
|
|
@ -181,12 +182,9 @@ log_hanlder_info(#{id := Id, level := Level, module := logger_std_h,
|
||||||
Type =:= standard_error ->
|
Type =:= standard_error ->
|
||||||
{Id, Level, console};
|
{Id, Level, console};
|
||||||
log_hanlder_info(#{id := Id, level := Level, module := logger_std_h,
|
log_hanlder_info(#{id := Id, level := Level, module := logger_std_h,
|
||||||
config := #{type := Type}}) ->
|
config := Config = #{type := file}}) ->
|
||||||
case Type of
|
{Id, Level, maps:get(file, Config, atom_to_list(Id))};
|
||||||
{file, Filename} -> {Id, Level, Filename};
|
|
||||||
{file, Filename, _Opts} -> {Id, Level, Filename};
|
|
||||||
_ -> {Id, Level, unknown}
|
|
||||||
end;
|
|
||||||
log_hanlder_info(#{id := Id, level := Level, module := logger_disk_log_h,
|
log_hanlder_info(#{id := Id, level := Level, module := logger_disk_log_h,
|
||||||
config := #{file := Filename}}) ->
|
config := #{file := Filename}}) ->
|
||||||
{Id, Level, Filename};
|
{Id, Level, Filename};
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ format(#{level:=Level,msg:=Msg0,meta:=Meta},Config0)
|
||||||
end,
|
end,
|
||||||
Config#{chars_limit=>Size}
|
Config#{chars_limit=>Size}
|
||||||
end,
|
end,
|
||||||
string:trim(format_msg(Msg0,Meta,Config1));
|
format_msg(Msg0,Meta,Config1);
|
||||||
true ->
|
true ->
|
||||||
""
|
""
|
||||||
end,
|
end,
|
||||||
|
|
@ -138,7 +138,7 @@ to_string(X,_) when is_list(X) ->
|
||||||
_ -> io_lib:format(?FormatP,[X])
|
_ -> io_lib:format(?FormatP,[X])
|
||||||
end;
|
end;
|
||||||
to_string(X,_) ->
|
to_string(X,_) ->
|
||||||
io_lib:format("~s",[X]).
|
io_lib:format(?FormatP,[X]).
|
||||||
|
|
||||||
printable_list([]) ->
|
printable_list([]) ->
|
||||||
false;
|
false;
|
||||||
|
|
@ -200,7 +200,7 @@ do_format_msg({Format0,Args},Depth,Opts) ->
|
||||||
%% already been here - avoid failing cyclically
|
%% already been here - avoid failing cyclically
|
||||||
erlang:raise(C,R,S);
|
erlang:raise(C,R,S);
|
||||||
_ ->
|
_ ->
|
||||||
format_msg({FormatError,[Format0,Args]},Depth,Opts)
|
do_format_msg({FormatError,[Format0,Args]},Depth,Opts)
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -281,7 +281,7 @@ do_inc_recv(?PUBLISH_PACKET(QoS, _PktId)) ->
|
||||||
?QOS_0 -> inc('messages.qos0.received');
|
?QOS_0 -> inc('messages.qos0.received');
|
||||||
?QOS_1 -> inc('messages.qos1.received');
|
?QOS_1 -> inc('messages.qos1.received');
|
||||||
?QOS_2 -> inc('messages.qos2.received');
|
?QOS_2 -> inc('messages.qos2.received');
|
||||||
_ -> ignore
|
_ -> ok
|
||||||
end,
|
end,
|
||||||
inc('packets.publish.received');
|
inc('packets.publish.received');
|
||||||
do_inc_recv(?PACKET(?PUBACK)) ->
|
do_inc_recv(?PACKET(?PUBACK)) ->
|
||||||
|
|
@ -302,13 +302,12 @@ do_inc_recv(?PACKET(?DISCONNECT)) ->
|
||||||
inc('packets.disconnect.received');
|
inc('packets.disconnect.received');
|
||||||
do_inc_recv(?PACKET(?AUTH)) ->
|
do_inc_recv(?PACKET(?AUTH)) ->
|
||||||
inc('packets.auth.received');
|
inc('packets.auth.received');
|
||||||
do_inc_recv(_Packet) ->
|
do_inc_recv(_Packet) -> ok.
|
||||||
ignore.
|
|
||||||
|
|
||||||
%% @doc Inc packets sent. Will not count $SYS PUBLISH.
|
%% @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>>, _, _)) ->
|
inc_sent(?PUBLISH_PACKET(_QoS, <<"$SYS/", _/binary>>, _, _)) ->
|
||||||
ignore;
|
ok;
|
||||||
inc_sent(Packet) ->
|
inc_sent(Packet) ->
|
||||||
inc('packets.sent'),
|
inc('packets.sent'),
|
||||||
do_inc_sent(Packet).
|
do_inc_sent(Packet).
|
||||||
|
|
@ -349,8 +348,7 @@ do_inc_sent(?PACKET(?DISCONNECT)) ->
|
||||||
inc('packets.disconnect.sent');
|
inc('packets.disconnect.sent');
|
||||||
do_inc_sent(?PACKET(?AUTH)) ->
|
do_inc_sent(?PACKET(?AUTH)) ->
|
||||||
inc('packets.auth.sent');
|
inc('packets.auth.sent');
|
||||||
do_inc_sent(_Packet) ->
|
do_inc_sent(_Packet) -> ok.
|
||||||
ignore.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
|
|
@ -368,7 +366,7 @@ init([]) ->
|
||||||
Metric = #metric{name = Name, type = Type, idx = reserved_idx(Name)},
|
Metric = #metric{name = Name, type = Type, idx = reserved_idx(Name)},
|
||||||
true = ets:insert(?TAB, Metric),
|
true = ets:insert(?TAB, Metric),
|
||||||
ok = counters:put(CRef, Idx, 0)
|
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}.
|
{ok, #state{next_idx = ?RESERVED_IDX + 1}, hibernate}.
|
||||||
|
|
||||||
handle_call({create, Type, Name}, _From, State = #state{next_idx = ?MAX_SIZE}) ->
|
handle_call({create, Type, Name}, _From, State = #state{next_idx = ?MAX_SIZE}) ->
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
-module(emqx_misc).
|
-module(emqx_misc).
|
||||||
|
|
||||||
|
-compile(inline).
|
||||||
|
|
||||||
-include("types.hrl").
|
-include("types.hrl").
|
||||||
-include("logger.hrl").
|
-include("logger.hrl").
|
||||||
|
|
||||||
|
|
@ -26,22 +28,20 @@
|
||||||
, start_timer/2
|
, start_timer/2
|
||||||
, start_timer/3
|
, start_timer/3
|
||||||
, cancel_timer/1
|
, cancel_timer/1
|
||||||
|
, drain_deliver/0
|
||||||
|
, drain_down/1
|
||||||
|
, check_oom/1
|
||||||
|
, check_oom/2
|
||||||
|
, tune_heap_size/1
|
||||||
, proc_name/2
|
, proc_name/2
|
||||||
, proc_stats/0
|
, proc_stats/0
|
||||||
, proc_stats/1
|
, proc_stats/1
|
||||||
|
, rand_seed/0
|
||||||
|
, now_to_secs/1
|
||||||
|
, now_to_ms/1
|
||||||
, index_of/2
|
, index_of/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([ drain_deliver/0
|
|
||||||
, drain_deliver/1
|
|
||||||
, drain_down/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
-compile({inline,
|
|
||||||
[ start_timer/2
|
|
||||||
, start_timer/3
|
|
||||||
]}).
|
|
||||||
|
|
||||||
%% @doc Merge options
|
%% @doc Merge options
|
||||||
-spec(merge_opts(Opts, Opts) -> Opts when Opts :: proplists:proplist()).
|
-spec(merge_opts(Opts, Opts) -> Opts when Opts :: proplists:proplist()).
|
||||||
merge_opts(Defaults, Options) ->
|
merge_opts(Defaults, Options) ->
|
||||||
|
|
@ -101,7 +101,7 @@ start_timer(Interval, Msg) ->
|
||||||
|
|
||||||
-spec(start_timer(integer(), pid() | atom(), term()) -> reference()).
|
-spec(start_timer(integer(), pid() | atom(), term()) -> reference()).
|
||||||
start_timer(Interval, Dest, Msg) ->
|
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).
|
-spec(cancel_timer(maybe(reference())) -> ok).
|
||||||
cancel_timer(Timer) when is_reference(Timer) ->
|
cancel_timer(Timer) when is_reference(Timer) ->
|
||||||
|
|
@ -112,6 +112,68 @@ cancel_timer(Timer) when is_reference(Timer) ->
|
||||||
end;
|
end;
|
||||||
cancel_timer(_) -> ok.
|
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()).
|
-spec(proc_name(atom(), pos_integer()) -> atom()).
|
||||||
proc_name(Mod, Id) ->
|
proc_name(Mod, Id) ->
|
||||||
list_to_atom(lists:concat([Mod, "_", Id])).
|
list_to_atom(lists:concat([Mod, "_", Id])).
|
||||||
|
|
@ -132,32 +194,16 @@ proc_stats(Pid) ->
|
||||||
[{mailbox_len, Len}|ProcStats]
|
[{mailbox_len, Len}|ProcStats]
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% @doc Drain delivers from the channel's mailbox.
|
rand_seed() ->
|
||||||
drain_deliver() ->
|
rand:seed(exsplus, erlang:timestamp()).
|
||||||
drain_deliver([]).
|
|
||||||
|
|
||||||
drain_deliver(Acc) ->
|
-spec(now_to_secs(erlang:timestamp()) -> pos_integer()).
|
||||||
receive
|
now_to_secs({MegaSecs, Secs, _MicroSecs}) ->
|
||||||
Deliver = {deliver, _Topic, _Msg} ->
|
MegaSecs * 1000000 + Secs.
|
||||||
drain_deliver([Deliver|Acc])
|
|
||||||
after 0 ->
|
|
||||||
lists:reverse(Acc)
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% @doc Drain process down events.
|
-spec(now_to_ms(erlang:timestamp()) -> pos_integer()).
|
||||||
-spec(drain_down(pos_integer()) -> list(pid())).
|
now_to_ms({MegaSecs, Secs, MicroSecs}) ->
|
||||||
drain_down(Cnt) when Cnt > 0 ->
|
(MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000).
|
||||||
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.
|
|
||||||
|
|
||||||
%% lists:index_of/2
|
%% lists:index_of/2
|
||||||
index_of(E, L) ->
|
index_of(E, L) ->
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ on_client_connected(ClientInfo, ConnAck, ConnInfo, Env) ->
|
||||||
connack => ConnAck,
|
connack => ConnAck,
|
||||||
clean_start => CleanStart,
|
clean_start => CleanStart,
|
||||||
expiry_interval => ExpiryInterval,
|
expiry_interval => ExpiryInterval,
|
||||||
ts => emqx_time:now_ms()
|
ts => erlang:system_time(millisecond)
|
||||||
},
|
},
|
||||||
case emqx_json:safe_encode(Presence) of
|
case emqx_json:safe_encode(Presence) of
|
||||||
{ok, Payload} ->
|
{ok, Payload} ->
|
||||||
|
|
@ -78,7 +78,7 @@ on_client_disconnected(ClientInfo, Reason, ConnInfo, Env) ->
|
||||||
Presence = #{clientid => ClientId,
|
Presence = #{clientid => ClientId,
|
||||||
username => Username,
|
username => Username,
|
||||||
reason => reason(Reason),
|
reason => reason(Reason),
|
||||||
ts => emqx_time:now_ms()
|
ts => erlang:system_time(millisecond)
|
||||||
},
|
},
|
||||||
case emqx_json:safe_encode(Presence) of
|
case emqx_json:safe_encode(Presence) of
|
||||||
{ok, Payload} ->
|
{ok, Payload} ->
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
||||||
|
|
@ -21,14 +21,14 @@
|
||||||
|
|
||||||
-export([ get_counters/1
|
-export([ get_counters/1
|
||||||
, get_counter/1
|
, get_counter/1
|
||||||
, update_counter/2
|
, inc_counter/2
|
||||||
, reset_counter/1
|
, reset_counter/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-compile({inline,
|
-compile({inline,
|
||||||
[ get_counters/1
|
[ get_counters/1
|
||||||
, get_counter/1
|
, get_counter/1
|
||||||
, update_counter/2
|
, inc_counter/2
|
||||||
, reset_counter/1
|
, reset_counter/1
|
||||||
]}).
|
]}).
|
||||||
|
|
||||||
|
|
@ -42,8 +42,8 @@ get_counters(Keys) when is_list(Keys) ->
|
||||||
get_counter(Key) ->
|
get_counter(Key) ->
|
||||||
case get(Key) of undefined -> 0; Cnt -> Cnt end.
|
case get(Key) of undefined -> 0; Cnt -> Cnt end.
|
||||||
|
|
||||||
-spec(update_counter(key(), number()) -> maybe(number())).
|
-spec(inc_counter(key(), number()) -> maybe(number())).
|
||||||
update_counter(Key, Inc) ->
|
inc_counter(Key, Inc) ->
|
||||||
put(Key, get_counter(Key) + Inc).
|
put(Key, get_counter(Key) + Inc).
|
||||||
|
|
||||||
-spec(reset_counter(key()) -> number()).
|
-spec(reset_counter(key()) -> number()).
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,11 @@
|
||||||
-module(emqx_rpc).
|
-module(emqx_rpc).
|
||||||
|
|
||||||
-export([ call/4
|
-export([ call/4
|
||||||
|
, call/5
|
||||||
, cast/4
|
, cast/4
|
||||||
|
, cast/5
|
||||||
, multicall/4
|
, multicall/4
|
||||||
|
, multicall/5
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-compile({inline,
|
-compile({inline,
|
||||||
|
|
@ -34,15 +37,27 @@
|
||||||
call(Node, Mod, Fun, Args) ->
|
call(Node, Mod, Fun, Args) ->
|
||||||
filter_result(?RPC:call(rpc_node(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) ->
|
multicall(Nodes, Mod, Fun, Args) ->
|
||||||
filter_result(?RPC:multicall(rpc_nodes(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) ->
|
cast(Node, Mod, Fun, Args) ->
|
||||||
filter_result(?RPC:cast(rpc_node(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),
|
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) ->
|
||||||
rpc_nodes(Nodes, []).
|
rpc_nodes(Nodes, []).
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,11 @@
|
||||||
|
|
||||||
-logger_header("[Session]").
|
-logger_header("[Session]").
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
-endif.
|
||||||
|
|
||||||
-export([init/2]).
|
-export([init/2]).
|
||||||
|
|
||||||
-export([ info/1
|
-export([ info/1
|
||||||
|
|
@ -58,9 +63,6 @@
|
||||||
, stats/1
|
, stats/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% Exports for unit tests
|
|
||||||
-export([set_field/3]).
|
|
||||||
|
|
||||||
-export([ subscribe/4
|
-export([ subscribe/4
|
||||||
, unsubscribe/3
|
, unsubscribe/3
|
||||||
]).
|
]).
|
||||||
|
|
@ -84,6 +86,9 @@
|
||||||
|
|
||||||
-export([expire/2]).
|
-export([expire/2]).
|
||||||
|
|
||||||
|
%% export for ct
|
||||||
|
-export([set_field/3]).
|
||||||
|
|
||||||
-export_type([session/0]).
|
-export_type([session/0]).
|
||||||
|
|
||||||
-import(emqx_zone, [get_env/3]).
|
-import(emqx_zone, [get_env/3]).
|
||||||
|
|
@ -95,11 +100,10 @@
|
||||||
max_subscriptions :: non_neg_integer(),
|
max_subscriptions :: non_neg_integer(),
|
||||||
%% Upgrade QoS?
|
%% Upgrade QoS?
|
||||||
upgrade_qos :: boolean(),
|
upgrade_qos :: boolean(),
|
||||||
%% Client <- Broker:
|
%% Client <- Broker: QoS1/2 messages sent to the client but unacked.
|
||||||
%% Inflight QoS1, QoS2 messages sent to the client but unacked.
|
|
||||||
inflight :: emqx_inflight:inflight(),
|
inflight :: emqx_inflight:inflight(),
|
||||||
%% All QoS1, QoS2 messages published to when client is disconnected.
|
%% All QoS1/2 messages published to when client is disconnected,
|
||||||
%% QoS 1 and QoS 2 messages pending transmission to the Client.
|
%% or QoS1/2 messages pending transmission to the Client.
|
||||||
%%
|
%%
|
||||||
%% Optionally, QoS0 messages pending transmission to the Client.
|
%% Optionally, QoS0 messages pending transmission to the Client.
|
||||||
mqueue :: emqx_mqueue:mqueue(),
|
mqueue :: emqx_mqueue:mqueue(),
|
||||||
|
|
@ -107,15 +111,14 @@
|
||||||
next_pkt_id = 1 :: emqx_types:packet_id(),
|
next_pkt_id = 1 :: emqx_types:packet_id(),
|
||||||
%% Retry interval for redelivering QoS1/2 messages
|
%% Retry interval for redelivering QoS1/2 messages
|
||||||
retry_interval :: timeout(),
|
retry_interval :: timeout(),
|
||||||
%% Client -> Broker:
|
%% Client -> Broker: QoS2 messages received from client and waiting for pubrel.
|
||||||
%% Inflight QoS2 messages received from client and waiting for pubrel.
|
|
||||||
awaiting_rel :: map(),
|
awaiting_rel :: map(),
|
||||||
%% Max Packets Awaiting PUBREL
|
%% Max Packets Awaiting PUBREL
|
||||||
max_awaiting_rel :: non_neg_integer(),
|
max_awaiting_rel :: non_neg_integer(),
|
||||||
%% Awaiting PUBREL Timeout
|
%% Awaiting PUBREL Timeout
|
||||||
await_rel_timeout :: timeout(),
|
awaiting_rel_timeout :: timeout(),
|
||||||
%% Enqueue Count
|
%% Deliver Stats
|
||||||
enqueue_cnt :: non_neg_integer(),
|
deliver_stats :: emqx_types:stats(),
|
||||||
%% Created at
|
%% Created at
|
||||||
created_at :: pos_integer()
|
created_at :: pos_integer()
|
||||||
}).
|
}).
|
||||||
|
|
@ -126,11 +129,13 @@
|
||||||
|
|
||||||
-define(DEFAULT_BATCH_N, 1000).
|
-define(DEFAULT_BATCH_N, 1000).
|
||||||
|
|
||||||
-define(ATTR_KEYS, [inflight_max,
|
-define(ATTR_KEYS, [inflight_cnt,
|
||||||
|
inflight_max,
|
||||||
|
mqueue_len,
|
||||||
mqueue_max,
|
mqueue_max,
|
||||||
retry_interval,
|
retry_interval,
|
||||||
awaiting_rel_max,
|
awaiting_rel_max,
|
||||||
await_rel_timeout,
|
awaiting_rel_timeout,
|
||||||
created_at
|
created_at
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
|
@ -146,7 +151,7 @@
|
||||||
next_pkt_id,
|
next_pkt_id,
|
||||||
awaiting_rel,
|
awaiting_rel,
|
||||||
awaiting_rel_max,
|
awaiting_rel_max,
|
||||||
await_rel_timeout,
|
awaiting_rel_timeout,
|
||||||
created_at
|
created_at
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
|
@ -163,7 +168,7 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Init a session
|
%% Init a Session
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Init a session.
|
%% @doc Init a session.
|
||||||
|
|
@ -178,11 +183,11 @@ init(#{zone := Zone}, #{receive_maximum := MaxInflight}) ->
|
||||||
retry_interval = get_env(Zone, retry_interval, 0),
|
retry_interval = get_env(Zone, retry_interval, 0),
|
||||||
awaiting_rel = #{},
|
awaiting_rel = #{},
|
||||||
max_awaiting_rel = get_env(Zone, max_awaiting_rel, 100),
|
max_awaiting_rel = get_env(Zone, max_awaiting_rel, 100),
|
||||||
await_rel_timeout = get_env(Zone, await_rel_timeout, 3600*1000),
|
awaiting_rel_timeout = get_env(Zone, awaiting_rel_timeout, 3600*1000),
|
||||||
enqueue_cnt = 0,
|
|
||||||
created_at = erlang:system_time(second)
|
created_at = erlang:system_time(second)
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
%% @private init mq
|
||||||
init_mqueue(Zone) ->
|
init_mqueue(Zone) ->
|
||||||
emqx_mqueue:init(#{max_len => get_env(Zone, max_mqueue_len, 1000),
|
emqx_mqueue:init(#{max_len => get_env(Zone, max_mqueue_len, 1000),
|
||||||
store_qos0 => get_env(Zone, mqueue_store_qos0, true),
|
store_qos0 => get_env(Zone, mqueue_store_qos0, true),
|
||||||
|
|
@ -215,6 +220,8 @@ info(subscriptions_max, #session{max_subscriptions = MaxSubs}) ->
|
||||||
info(upgrade_qos, #session{upgrade_qos = UpgradeQoS}) ->
|
info(upgrade_qos, #session{upgrade_qos = UpgradeQoS}) ->
|
||||||
UpgradeQoS;
|
UpgradeQoS;
|
||||||
info(inflight, #session{inflight = Inflight}) ->
|
info(inflight, #session{inflight = Inflight}) ->
|
||||||
|
Inflight;
|
||||||
|
info(inflight_cnt, #session{inflight = Inflight}) ->
|
||||||
emqx_inflight:size(Inflight);
|
emqx_inflight:size(Inflight);
|
||||||
info(inflight_max, #session{inflight = Inflight}) ->
|
info(inflight_max, #session{inflight = Inflight}) ->
|
||||||
emqx_inflight:max_size(Inflight);
|
emqx_inflight:max_size(Inflight);
|
||||||
|
|
@ -229,13 +236,19 @@ info(mqueue_dropped, #session{mqueue = MQueue}) ->
|
||||||
info(next_pkt_id, #session{next_pkt_id = PacketId}) ->
|
info(next_pkt_id, #session{next_pkt_id = PacketId}) ->
|
||||||
PacketId;
|
PacketId;
|
||||||
info(awaiting_rel, #session{awaiting_rel = AwaitingRel}) ->
|
info(awaiting_rel, #session{awaiting_rel = AwaitingRel}) ->
|
||||||
|
AwaitingRel;
|
||||||
|
info(awaiting_rel_cnt, #session{awaiting_rel = AwaitingRel}) ->
|
||||||
maps:size(AwaitingRel);
|
maps:size(AwaitingRel);
|
||||||
info(awaiting_rel_max, #session{max_awaiting_rel = MaxAwaitingRel}) ->
|
info(awaiting_rel_max, #session{max_awaiting_rel = MaxAwaitingRel}) ->
|
||||||
MaxAwaitingRel;
|
MaxAwaitingRel;
|
||||||
info(await_rel_timeout, #session{await_rel_timeout = Timeout}) ->
|
info(awaiting_rel_timeout, #session{awaiting_rel_timeout = Timeout}) ->
|
||||||
Timeout;
|
Timeout;
|
||||||
info(enqueue_cnt, #session{enqueue_cnt = Cnt}) ->
|
info(enqueue_cnt, #session{deliver_stats = undefined}) ->
|
||||||
Cnt;
|
0;
|
||||||
|
info(enqueue_cnt, #session{deliver_stats = Stats}) ->
|
||||||
|
maps:get(enqueue_cnt, Stats, 0);
|
||||||
|
info(deliver_stats, #session{deliver_stats = Stats}) ->
|
||||||
|
Stats;
|
||||||
info(created_at, #session{created_at = CreatedAt}) ->
|
info(created_at, #session{created_at = CreatedAt}) ->
|
||||||
CreatedAt.
|
CreatedAt.
|
||||||
|
|
||||||
|
|
@ -329,8 +342,7 @@ unsubscribe(ClientInfo, TopicFilter, Session = #session{subscriptions = Subs}) -
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-spec(publish(emqx_types:packet_id(), emqx_types:message(), session())
|
-spec(publish(emqx_types:packet_id(), emqx_types:message(), session())
|
||||||
-> {ok, emqx_types:publish_result()} |
|
-> {ok, emqx_types:publish_result(), session()} |
|
||||||
{ok, emqx_types:publish_result(), session()} |
|
|
||||||
{error, emqx_types:reason_code()}).
|
{error, emqx_types:reason_code()}).
|
||||||
publish(PacketId, Msg = #message{qos = ?QOS_2}, Session) ->
|
publish(PacketId, Msg = #message{qos = ?QOS_2}, Session) ->
|
||||||
case is_awaiting_full(Session) of
|
case is_awaiting_full(Session) of
|
||||||
|
|
@ -341,8 +353,8 @@ publish(PacketId, Msg = #message{qos = ?QOS_2}, Session) ->
|
||||||
end;
|
end;
|
||||||
|
|
||||||
%% Publish QoS0/1 directly
|
%% Publish QoS0/1 directly
|
||||||
publish(_PacketId, Msg, _Session) ->
|
publish(_PacketId, Msg, Session) ->
|
||||||
{ok, emqx_broker:publish(Msg)}.
|
{ok, emqx_broker:publish(Msg), Session}.
|
||||||
|
|
||||||
is_awaiting_full(#session{max_awaiting_rel = 0}) ->
|
is_awaiting_full(#session{max_awaiting_rel = 0}) ->
|
||||||
false;
|
false;
|
||||||
|
|
@ -501,7 +513,7 @@ enqueue(Delivers, Session = #session{subscriptions = Subs}) when is_list(Deliver
|
||||||
|| {deliver, Topic, Msg} <- Delivers],
|
|| {deliver, Topic, Msg} <- Delivers],
|
||||||
lists:foldl(fun enqueue/2, Session, Msgs);
|
lists:foldl(fun enqueue/2, Session, Msgs);
|
||||||
|
|
||||||
enqueue(Msg, Session = #session{mqueue = Q, enqueue_cnt = Cnt})
|
enqueue(Msg, Session = #session{mqueue = Q})
|
||||||
when is_record(Msg, message) ->
|
when is_record(Msg, message) ->
|
||||||
{Dropped, NewQ} = emqx_mqueue:in(Msg, Q),
|
{Dropped, NewQ} = emqx_mqueue:in(Msg, Q),
|
||||||
if is_record(Dropped, message) ->
|
if is_record(Dropped, message) ->
|
||||||
|
|
@ -509,7 +521,7 @@ enqueue(Msg, Session = #session{mqueue = Q, enqueue_cnt = Cnt})
|
||||||
[emqx_message:format(Dropped)]);
|
[emqx_message:format(Dropped)]);
|
||||||
true -> ok
|
true -> ok
|
||||||
end,
|
end,
|
||||||
Session#session{mqueue = NewQ, enqueue_cnt = Cnt+1}.
|
inc_deliver_stats(enqueue_cnt, Session#session{mqueue = NewQ}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Awaiting ACK for QoS1/QoS2 Messages
|
%% Awaiting ACK for QoS1/QoS2 Messages
|
||||||
|
|
@ -612,11 +624,11 @@ expire_awaiting_rel([], _Now, Session) ->
|
||||||
|
|
||||||
expire_awaiting_rel([{PacketId, Ts} | More], Now,
|
expire_awaiting_rel([{PacketId, Ts} | More], Now,
|
||||||
Session = #session{awaiting_rel = AwaitingRel,
|
Session = #session{awaiting_rel = AwaitingRel,
|
||||||
await_rel_timeout = Timeout}) ->
|
awaiting_rel_timeout = Timeout}) ->
|
||||||
case (timer:now_diff(Now, Ts) div 1000) of
|
case (timer:now_diff(Now, Ts) div 1000) of
|
||||||
Age when Age >= Timeout ->
|
Age when Age >= Timeout ->
|
||||||
ok = emqx_metrics:inc('messages.qos2.expired'),
|
ok = emqx_metrics:inc('messages.qos2.expired'),
|
||||||
?LOG(warning, "Dropped qos2 packet ~s for await_rel_timeout", [PacketId]),
|
?LOG(warning, "Dropped qos2 packet ~s for awaiting_rel_timeout", [PacketId]),
|
||||||
Session1 = Session#session{awaiting_rel = maps:remove(PacketId, AwaitingRel)},
|
Session1 = Session#session{awaiting_rel = maps:remove(PacketId, AwaitingRel)},
|
||||||
expire_awaiting_rel(More, Now, Session1);
|
expire_awaiting_rel(More, Now, Session1);
|
||||||
Age ->
|
Age ->
|
||||||
|
|
@ -633,3 +645,15 @@ next_pkt_id(Session = #session{next_pkt_id = 16#FFFF}) ->
|
||||||
next_pkt_id(Session = #session{next_pkt_id = Id}) ->
|
next_pkt_id(Session = #session{next_pkt_id = Id}) ->
|
||||||
Session#session{next_pkt_id = Id + 1}.
|
Session#session{next_pkt_id = Id + 1}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Helper functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
inc_deliver_stats(Key, Session) ->
|
||||||
|
inc_deliver_stats(Key, 1, Session).
|
||||||
|
inc_deliver_stats(Key, I, Session = #session{deliver_stats = undefined}) ->
|
||||||
|
Session#session{deliver_stats = #{Key => I}};
|
||||||
|
inc_deliver_stats(Key, I, Session = #session{deliver_stats = Stats}) ->
|
||||||
|
NStats = maps:update_with(Key, fun(V) -> V+I end, I, Stats),
|
||||||
|
Session#session{deliver_stats = NStats}.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,4 +49,3 @@ now_ms() ->
|
||||||
-spec(now_ms(erlang:timestamp()) -> pos_integer()).
|
-spec(now_ms(erlang:timestamp()) -> pos_integer()).
|
||||||
now_ms({MegaSecs, Secs, MicroSecs}) ->
|
now_ms({MegaSecs, Secs, MicroSecs}) ->
|
||||||
(MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000).
|
(MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,8 @@
|
||||||
, stats/0
|
, stats/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-export_type([oom_policy/0]).
|
||||||
|
|
||||||
-type(ver() :: ?MQTT_PROTO_V3
|
-type(ver() :: ?MQTT_PROTO_V3
|
||||||
| ?MQTT_PROTO_V4
|
| ?MQTT_PROTO_V4
|
||||||
| ?MQTT_PROTO_V5).
|
| ?MQTT_PROTO_V5).
|
||||||
|
|
@ -186,3 +188,7 @@
|
||||||
-type(infos() :: #{atom() => term()}).
|
-type(infos() :: #{atom() => term()}).
|
||||||
-type(stats() :: #{atom() => non_neg_integer()|stats()}).
|
-type(stats() :: #{atom() => non_neg_integer()|stats()}).
|
||||||
|
|
||||||
|
-type(oom_policy() :: #{message_queue_len => non_neg_integer(),
|
||||||
|
max_heap_size => non_neg_integer()
|
||||||
|
}).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,11 @@
|
||||||
, get_port_info/1
|
, get_port_info/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
-endif.
|
||||||
|
|
||||||
-export([cpu_util/0]).
|
-export([cpu_util/0]).
|
||||||
|
|
||||||
-define(UTIL_ALLOCATORS, [temp_alloc,
|
-define(UTIL_ALLOCATORS, [temp_alloc,
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,11 @@
|
||||||
|
|
||||||
-logger_header("[MQTT/WS]").
|
-logger_header("[MQTT/WS]").
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
-endif.
|
||||||
|
|
||||||
%% API
|
%% API
|
||||||
-export([ info/1
|
-export([ info/1
|
||||||
, stats/1
|
, stats/1
|
||||||
|
|
@ -40,6 +45,11 @@
|
||||||
, terminate/3
|
, terminate/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-import(emqx_misc,
|
||||||
|
[ maybe_apply/2
|
||||||
|
, start_timer/2
|
||||||
|
]).
|
||||||
|
|
||||||
-record(state, {
|
-record(state, {
|
||||||
%% Peername of the ws connection.
|
%% Peername of the ws connection.
|
||||||
peername :: emqx_types:peername(),
|
peername :: emqx_types:peername(),
|
||||||
|
|
@ -47,24 +57,41 @@
|
||||||
sockname :: emqx_types:peername(),
|
sockname :: emqx_types:peername(),
|
||||||
%% Sock state
|
%% Sock state
|
||||||
sockstate :: emqx_types:sockstate(),
|
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(),
|
parse_state :: emqx_frame:parse_state(),
|
||||||
%% Serialize function
|
%% Serialize function
|
||||||
serialize :: emqx_frame:serialize_fun(),
|
serialize :: emqx_frame:serialize_fun(),
|
||||||
%% Channel
|
%% Channel
|
||||||
channel :: emqx_channel:channel(),
|
channel :: emqx_channel:channel(),
|
||||||
|
%% GC State
|
||||||
|
gc_state :: maybe(emqx_gc:gc_state()),
|
||||||
%% Out Pending Packets
|
%% Out Pending Packets
|
||||||
pendings :: list(emqx_types:packet()),
|
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
|
%% The stop reason
|
||||||
stop_reason :: term()
|
stop_reason :: term()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type(state() :: #state{}).
|
-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(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt]).
|
||||||
-define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]).
|
-define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]).
|
||||||
|
|
||||||
|
-define(ENABLED(X), (X =/= undefined)).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% API
|
%% API
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
@ -87,8 +114,19 @@ info(sockname, #state{sockname = Sockname}) ->
|
||||||
Sockname;
|
Sockname;
|
||||||
info(sockstate, #state{sockstate = SockSt}) ->
|
info(sockstate, #state{sockstate = SockSt}) ->
|
||||||
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}) ->
|
info(channel, #state{channel = Channel}) ->
|
||||||
emqx_channel:info(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()).
|
-spec(stats(pid()|state()) -> emqx_types:stats()).
|
||||||
stats(WsPid) when is_pid(WsPid) ->
|
stats(WsPid) when is_pid(WsPid) ->
|
||||||
|
|
@ -121,6 +159,7 @@ call(WsPid, Req) when is_pid(WsPid) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init(Req, Opts) ->
|
init(Req, Opts) ->
|
||||||
|
%% WS Transport Idle Timeout
|
||||||
IdleTimeout = proplists:get_value(idle_timeout, Opts, 7200000),
|
IdleTimeout = proplists:get_value(idle_timeout, Opts, 7200000),
|
||||||
DeflateOptions = maps:from_list(proplists:get_value(deflate_options, Opts, [])),
|
DeflateOptions = maps:from_list(proplists:get_value(deflate_options, Opts, [])),
|
||||||
MaxFrameSize = case proplists:get_value(max_frame_size, Opts, 0) of
|
MaxFrameSize = case proplists:get_value(max_frame_size, Opts, 0) of
|
||||||
|
|
@ -167,29 +206,41 @@ websocket_init([Req, Opts]) ->
|
||||||
conn_mod => ?MODULE
|
conn_mod => ?MODULE
|
||||||
},
|
},
|
||||||
Zone = proplists:get_value(zone, Opts),
|
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),
|
ParseState = emqx_frame:initial_parse_state(FrameOpts),
|
||||||
Serialize = emqx_frame:serialize_fun(),
|
Serialize = emqx_frame:serialize_fun(),
|
||||||
Channel = emqx_channel:init(ConnInfo, Opts),
|
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)),
|
emqx_logger:set_metadata_peername(esockd_net:format(Peername)),
|
||||||
{ok, #state{peername = Peername,
|
{ok, #state{peername = Peername,
|
||||||
sockname = Sockname,
|
sockname = Sockname,
|
||||||
sockstate = idle,
|
sockstate = running,
|
||||||
|
active_n = ActiveN,
|
||||||
|
limiter = Limiter,
|
||||||
parse_state = ParseState,
|
parse_state = ParseState,
|
||||||
serialize = Serialize,
|
serialize = Serialize,
|
||||||
channel = Channel,
|
channel = Channel,
|
||||||
pendings = []
|
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, Data}, State) when is_list(Data) ->
|
||||||
websocket_handle({binary, iolist_to_binary(Data)}, State);
|
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]),
|
?LOG(debug, "RECV ~p", [Data]),
|
||||||
Oct = iolist_size(Data),
|
ok = inc_recv_stats(1, iolist_size(Data)),
|
||||||
ok = inc_recv_stats(1, Oct),
|
parse_incoming(Data, ensure_stats_timer(State));
|
||||||
{ok, NChannel} = emqx_channel:handle_in(Oct, Channel),
|
|
||||||
process_incoming(Data, State#state{channel = NChannel});
|
|
||||||
|
|
||||||
%% Pings should be replied with pongs, cowboy does it automatically
|
%% Pings should be replied with pongs, cowboy does it automatically
|
||||||
%% Pongs can be safely ignored. Clause here simply prevents crash.
|
%% Pongs can be safely ignored. Clause here simply prevents crash.
|
||||||
|
|
@ -208,30 +259,43 @@ websocket_info({call, From, Req}, State) ->
|
||||||
handle_call(From, Req, State);
|
handle_call(From, Req, State);
|
||||||
|
|
||||||
websocket_info({cast, Msg}, State = #state{channel = Channel}) ->
|
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),
|
Serialize = emqx_frame:serialize_fun(ConnPkt),
|
||||||
NState = State#state{sockstate = running,
|
NState = State#state{serialize = Serialize,
|
||||||
serialize = Serialize
|
idle_timer = undefined
|
||||||
},
|
},
|
||||||
handle_incoming(Packet, NState);
|
handle_incoming(Packet, NState);
|
||||||
|
|
||||||
|
websocket_info({incoming, ?PACKET(?PINGREQ)}, State) ->
|
||||||
|
reply(?PACKET(?PINGRESP), State);
|
||||||
|
|
||||||
websocket_info({incoming, Packet}, State) ->
|
websocket_info({incoming, Packet}, State) ->
|
||||||
handle_incoming(Packet, State);
|
handle_incoming(Packet, State);
|
||||||
|
|
||||||
websocket_info(Deliver = {deliver, _Topic, _Msg},
|
websocket_info(rate_limit, State) ->
|
||||||
State = #state{channel = Channel}) ->
|
InStats = #{cnt => emqx_pd:reset_counter(incoming_pubs),
|
||||||
Delivers = emqx_misc:drain_deliver([Deliver]),
|
oct => emqx_pd:reset_counter(incoming_bytes)
|
||||||
Result = emqx_channel:handle_out(Delivers, Channel),
|
},
|
||||||
handle_return(Result, State);
|
erlang:send(self(), {check_gc, InStats}),
|
||||||
|
ensure_rate_limit(InStats, State);
|
||||||
|
|
||||||
websocket_info({timeout, TRef, keepalive}, State) when is_reference(TRef) ->
|
websocket_info({check_gc, Stats}, State) ->
|
||||||
RecvOct = emqx_pd:get_counter(recv_oct),
|
{ok, check_oom(run_gc(Stats, State))};
|
||||||
handle_timeout(TRef, {keepalive, RecvOct}, State);
|
|
||||||
|
|
||||||
websocket_info({timeout, TRef, emit_stats}, State) when is_reference(TRef) ->
|
websocket_info({deliver, _Topic, _Msg} = Deliver, State = #state{channel = Channel}) ->
|
||||||
handle_timeout(TRef, {emit_stats, stats(State)}, State);
|
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) ->
|
websocket_info({timeout, TRef, Msg}, State) when is_reference(TRef) ->
|
||||||
handle_timeout(TRef, Msg, State);
|
handle_timeout(TRef, Msg, State);
|
||||||
|
|
@ -282,38 +346,107 @@ handle_call(From, Req, State = #state{channel = Channel}) ->
|
||||||
stop(Reason, enqueue(OutPacket, NState))
|
stop(Reason, enqueue(OutPacket, NState))
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Handle timeout
|
|
||||||
|
|
||||||
handle_timeout(TRef, Msg, State = #state{channel = Channel}) ->
|
|
||||||
handle_return(emqx_channel:handle_timeout(TRef, Msg, Channel), State).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Handle Info
|
%% Handle Info
|
||||||
|
|
||||||
handle_info({enter, _}, State = #state{channel = Channel}) ->
|
handle_info({connack, ConnAck}, State = #state{channel = Channel}) ->
|
||||||
ChanAttrs = emqx_channel:attrs(Channel),
|
#{clientid := ClientId} = emqx_channel:info(clientinfo, Channel),
|
||||||
SockAttrs = maps:from_list(info(?INFO_KEYS, State)),
|
ok = emqx_cm:register_channel(ClientId),
|
||||||
Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}),
|
ok = emqx_cm:set_chan_attrs(ClientId, attrs(State)),
|
||||||
ok = emqx_channel:handle_info({register, Attrs, stats(State)}, Channel),
|
ok = emqx_cm:set_chan_stats(ClientId, stats(State)),
|
||||||
|
reply(enqueue(ConnAck, 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)),
|
||||||
reply(State);
|
reply(State);
|
||||||
|
|
||||||
handle_info(Info, State = #state{channel = Channel}) ->
|
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).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Process incoming data
|
%% Handle timeout
|
||||||
|
|
||||||
process_incoming(<<>>, 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
|
||||||
|
|
||||||
|
parse_incoming(<<>>, State) ->
|
||||||
{ok, State};
|
{ok, State};
|
||||||
|
|
||||||
process_incoming(Data, State = #state{parse_state = ParseState}) ->
|
parse_incoming(Data, State = #state{parse_state = ParseState}) ->
|
||||||
try emqx_frame:parse(Data, ParseState) of
|
try emqx_frame:parse(Data, ParseState) of
|
||||||
{more, NParseState} ->
|
{more, NParseState} ->
|
||||||
{ok, State#state{parse_state = NParseState}};
|
{ok, State#state{parse_state = NParseState}};
|
||||||
{ok, Packet, Rest, NParseState} ->
|
{ok, Packet, Rest, NParseState} ->
|
||||||
self() ! {incoming, Packet},
|
erlang:send(self(), {incoming, Packet}),
|
||||||
process_incoming(Rest, State#state{parse_state = NParseState})
|
parse_incoming(Rest, State#state{parse_state = NParseState})
|
||||||
catch
|
catch
|
||||||
error:Reason:Stk ->
|
error:Reason:Stk ->
|
||||||
?LOG(error, "~nParse failed for ~p~nStacktrace: ~p~nFrame data: ~p",
|
?LOG(error, "~nParse failed for ~p~nStacktrace: ~p~nFrame data: ~p",
|
||||||
|
|
@ -323,52 +456,60 @@ process_incoming(Data, State = #state{parse_state = ParseState}) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Handle incoming packets
|
%% Handle incoming packet
|
||||||
|
|
||||||
handle_incoming(Packet = ?PACKET(Type), State = #state{channel = Channel}) ->
|
handle_incoming(Packet, State = #state{active_n = ActiveN, channel = Channel})
|
||||||
_ = inc_incoming_stats(Type),
|
when is_record(Packet, mqtt_packet) ->
|
||||||
_ = emqx_metrics:inc_recv(Packet),
|
|
||||||
?LOG(debug, "RECV ~s", [emqx_packet:format(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_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 channel return
|
||||||
|
|
||||||
handle_return(ok, State) ->
|
handle_chan_return(ok, State) ->
|
||||||
reply(State);
|
reply(State);
|
||||||
handle_return({ok, NChannel}, State) ->
|
handle_chan_return({ok, NChannel}, State) ->
|
||||||
reply(State#state{channel= NChannel});
|
reply(State#state{channel= NChannel});
|
||||||
handle_return({ok, Replies, NChannel}, State) ->
|
handle_chan_return({ok, Replies, NChannel}, State) ->
|
||||||
reply(Replies, State#state{channel= NChannel});
|
reply(Replies, State#state{channel= NChannel});
|
||||||
handle_return({stop, Reason, NChannel}, State) ->
|
handle_chan_return({shutdown, Reason, NChannel}, State) ->
|
||||||
stop(Reason, State#state{channel = NChannel});
|
stop(Reason, State#state{channel = NChannel});
|
||||||
handle_return({stop, Reason, OutPacket, NChannel}, State) ->
|
handle_chan_return({shutdown, Reason, OutPacket, NChannel}, State) ->
|
||||||
NState = State#state{channel = NChannel},
|
NState = State#state{channel = NChannel},
|
||||||
stop(Reason, enqueue(OutPacket, NState)).
|
stop(Reason, enqueue(OutPacket, NState)).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Handle outgoing packets
|
%% 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),
|
IoData = lists:map(serialize_and_inc_stats_fun(State), Packets),
|
||||||
Oct = iolist_size(IoData),
|
Oct = iolist_size(IoData),
|
||||||
ok = inc_sent_stats(length(Packets), Oct),
|
ok = inc_sent_stats(length(Packets), Oct),
|
||||||
{ok, NChannel} = emqx_channel:handle_out(Oct, Channel),
|
case emqx_pd:get_counter(outgoing_pubs) > ActiveN of
|
||||||
{{binary, IoData}, State#state{channel = NChannel}}.
|
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}) ->
|
serialize_and_inc_stats_fun(#state{serialize = Serialize}) ->
|
||||||
fun(Packet = ?PACKET(Type)) ->
|
fun(Packet) ->
|
||||||
case Serialize(Packet) of
|
case Serialize(Packet) of
|
||||||
<<>> -> ?LOG(warning, "~s is discarded due to the frame is too large!",
|
<<>> -> ?LOG(warning, "~s is discarded due to the frame is too large!",
|
||||||
[emqx_packet:format(Packet)]),
|
[emqx_packet:format(Packet)]),
|
||||||
<<>>;
|
<<>>;
|
||||||
Data -> _ = inc_outgoing_stats(Type),
|
Data -> ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]),
|
||||||
_ = emqx_metrics:inc_sent(Packet),
|
ok = inc_outgoing_stats(Packet),
|
||||||
?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]),
|
|
||||||
Data
|
Data
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
@ -384,23 +525,33 @@ serialize_and_inc_stats_fun(#state{serialize = Serialize}) ->
|
||||||
]}).
|
]}).
|
||||||
|
|
||||||
inc_recv_stats(Cnt, Oct) ->
|
inc_recv_stats(Cnt, Oct) ->
|
||||||
emqx_pd:update_counter(recv_cnt, Cnt),
|
emqx_pd:inc_counter(incoming_bytes, Oct),
|
||||||
emqx_pd:update_counter(recv_oct, Oct),
|
emqx_pd:inc_counter(recv_cnt, Cnt),
|
||||||
|
emqx_pd:inc_counter(recv_oct, Oct),
|
||||||
emqx_metrics:inc('bytes.received', Oct).
|
emqx_metrics:inc('bytes.received', Oct).
|
||||||
|
|
||||||
inc_incoming_stats(Type) ->
|
inc_incoming_stats(Packet = ?PACKET(Type)) ->
|
||||||
emqx_pd:update_counter(recv_pkt, 1),
|
emqx_pd:inc_counter(recv_pkt, 1),
|
||||||
(Type == ?PUBLISH)
|
if Type == ?PUBLISH ->
|
||||||
andalso emqx_pd:update_counter(recv_msg, 1).
|
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) ->
|
inc_outgoing_stats(Packet = ?PACKET(Type)) ->
|
||||||
emqx_pd:update_counter(send_pkt, 1),
|
emqx_pd:inc_counter(send_pkt, 1),
|
||||||
(Type == ?PUBLISH)
|
if Type == ?PUBLISH ->
|
||||||
andalso emqx_pd:update_counter(send_msg, 1).
|
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) ->
|
inc_sent_stats(Cnt, Oct) ->
|
||||||
emqx_pd:update_counter(send_cnt, Cnt),
|
emqx_pd:inc_counter(outgoing_bytes, Oct),
|
||||||
emqx_pd:update_counter(send_oct, Oct),
|
emqx_pd:inc_counter(send_cnt, Cnt),
|
||||||
|
emqx_pd:inc_counter(send_oct, Oct),
|
||||||
emqx_metrics:inc('bytes.sent', Oct).
|
emqx_metrics:inc('bytes.sent', Oct).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
@ -437,6 +588,9 @@ enqueue(Packet, State) when is_record(Packet, mqtt_packet) ->
|
||||||
enqueue(Packets, State = #state{pendings = Pendings}) ->
|
enqueue(Packets, State = #state{pendings = Pendings}) ->
|
||||||
State#state{pendings = lists:append(Pendings, Packets)}.
|
State#state{pendings = lists:append(Pendings, Packets)}.
|
||||||
|
|
||||||
|
shutdown(Reason, State) ->
|
||||||
|
stop({shutdown, Reason}, State).
|
||||||
|
|
||||||
stop(Reason, State = #state{pendings = []}) ->
|
stop(Reason, State = #state{pendings = []}) ->
|
||||||
{stop, State#state{stop_reason = Reason}};
|
{stop, State#state{stop_reason = Reason}};
|
||||||
stop(Reason, State = #state{pendings = Pendings}) ->
|
stop(Reason, State = #state{pendings = Pendings}) ->
|
||||||
|
|
|
||||||
|
|
@ -25,31 +25,63 @@
|
||||||
|
|
||||||
-logger_header("[Zone]").
|
-logger_header("[Zone]").
|
||||||
|
|
||||||
%% APIs
|
-compile({inline,
|
||||||
-export([start_link/0, stop/0]).
|
[ idle_timeout/1
|
||||||
|
, publish_limit/1
|
||||||
-export([ frame_options/1
|
, mqtt_frame_options/1
|
||||||
, mqtt_strict_mode/1
|
, mqtt_strict_mode/1
|
||||||
, max_packet_size/1
|
, max_packet_size/1
|
||||||
|
, mountpoint/1
|
||||||
, use_username_as_clientid/1
|
, use_username_as_clientid/1
|
||||||
|
, stats_timer/1
|
||||||
, enable_stats/1
|
, enable_stats/1
|
||||||
, enable_acl/1
|
, enable_acl/1
|
||||||
, enable_ban/1
|
, enable_ban/1
|
||||||
, enable_flapping_detect/1
|
, enable_flapping_detect/1
|
||||||
, ignore_loop_deliver/1
|
, ignore_loop_deliver/1
|
||||||
, server_keepalive/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]).
|
||||||
|
|
||||||
|
%% 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
|
, max_inflight/1
|
||||||
, session_expiry_interval/1
|
, session_expiry_interval/1
|
||||||
, force_gc_policy/1
|
, force_gc_policy/1
|
||||||
, force_shutdown_policy/1
|
, force_shutdown_policy/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([check_oom/2]).
|
-export([ init_gc_state/1
|
||||||
|
, oom_policy/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% Zone API
|
||||||
-export([ get_env/2
|
-export([ get_env/2
|
||||||
, get_env/3
|
, get_env/3
|
||||||
, set_env/3
|
, set_env/3
|
||||||
, unset_env/2
|
, unset_env/2
|
||||||
|
, unset_all_env/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([force_reload/0]).
|
-export([force_reload/0]).
|
||||||
|
|
@ -63,27 +95,46 @@
|
||||||
, code_change/3
|
, code_change/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export_type([zone/0]).
|
-import(emqx_misc, [maybe_apply/2]).
|
||||||
|
|
||||||
%% dummy state
|
-export_type([zone/0]).
|
||||||
-record(state, {}).
|
|
||||||
|
|
||||||
-type(zone() :: atom()).
|
-type(zone() :: atom()).
|
||||||
|
|
||||||
-define(TAB, ?MODULE).
|
-define(TAB, ?MODULE).
|
||||||
-define(SERVER, ?MODULE).
|
-define(SERVER, ?MODULE).
|
||||||
|
-define(DEFAULT_IDLE_TIMEOUT, 30000).
|
||||||
-define(KEY(Zone, Key), {?MODULE, Zone, Key}).
|
-define(KEY(Zone, Key), {?MODULE, Zone, Key}).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% APIs
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
-spec(start_link() -> startlink_ret()).
|
-spec(start_link() -> startlink_ret()).
|
||||||
start_link() ->
|
start_link() ->
|
||||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||||
|
|
||||||
-spec(frame_options(zone()) -> emqx_frame:options()).
|
-spec(stop() -> ok).
|
||||||
frame_options(Zone) ->
|
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),
|
#{strict_mode => mqtt_strict_mode(Zone),
|
||||||
max_size => max_packet_size(Zone)
|
max_size => max_packet_size(Zone)
|
||||||
}.
|
}.
|
||||||
|
|
@ -96,10 +147,17 @@ mqtt_strict_mode(Zone) ->
|
||||||
max_packet_size(Zone) ->
|
max_packet_size(Zone) ->
|
||||||
get_env(Zone, max_packet_size, ?MAX_PACKET_SIZE).
|
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()).
|
-spec(use_username_as_clientid(zone()) -> boolean()).
|
||||||
use_username_as_clientid(Zone) ->
|
use_username_as_clientid(Zone) ->
|
||||||
get_env(Zone, use_username_as_clientid, false).
|
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()).
|
-spec(enable_stats(zone()) -> boolean()).
|
||||||
enable_stats(Zone) ->
|
enable_stats(Zone) ->
|
||||||
get_env(Zone, enable_stats, true).
|
get_env(Zone, enable_stats, true).
|
||||||
|
|
@ -124,6 +182,10 @@ ignore_loop_deliver(Zone) ->
|
||||||
server_keepalive(Zone) ->
|
server_keepalive(Zone) ->
|
||||||
get_env(Zone, server_keepalive).
|
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).
|
-spec(max_inflight(zone()) -> 0..65535).
|
||||||
max_inflight(Zone) ->
|
max_inflight(Zone) ->
|
||||||
get_env(Zone, max_inflight, 65535).
|
get_env(Zone, max_inflight, 65535).
|
||||||
|
|
@ -140,18 +202,9 @@ force_gc_policy(Zone) ->
|
||||||
force_shutdown_policy(Zone) ->
|
force_shutdown_policy(Zone) ->
|
||||||
get_env(Zone, force_shutdown_policy).
|
get_env(Zone, force_shutdown_policy).
|
||||||
|
|
||||||
-spec(check_oom(zone(), fun()) -> ok | term()).
|
%%--------------------------------------------------------------------
|
||||||
check_oom(Zone, Action) ->
|
%% APIs
|
||||||
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.
|
|
||||||
|
|
||||||
-spec(get_env(maybe(zone()), atom()) -> maybe(term())).
|
-spec(get_env(maybe(zone()), atom()) -> maybe(term())).
|
||||||
get_env(undefined, Key) -> emqx:get_env(Key);
|
get_env(undefined, Key) -> emqx:get_env(Key);
|
||||||
|
|
@ -175,21 +228,22 @@ set_env(Zone, Key, Val) ->
|
||||||
unset_env(Zone, Key) ->
|
unset_env(Zone, Key) ->
|
||||||
persistent_term:erase(?KEY(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).
|
-spec(force_reload() -> ok).
|
||||||
force_reload() ->
|
force_reload() ->
|
||||||
gen_server:call(?SERVER, force_reload).
|
gen_server:call(?SERVER, force_reload).
|
||||||
|
|
||||||
-spec(stop() -> ok).
|
|
||||||
stop() ->
|
|
||||||
gen_server:stop(?SERVER, normal, infinity).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
_ = do_reload(),
|
_ = do_reload(),
|
||||||
{ok, #state{}}.
|
{ok, #{}}.
|
||||||
|
|
||||||
handle_call(force_reload, _From, State) ->
|
handle_call(force_reload, _From, State) ->
|
||||||
_ = do_reload(),
|
_ = do_reload(),
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,18 @@ init_per_suite(Config) ->
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
emqx_ct_helpers:stop_apps([]).
|
emqx_ct_helpers:stop_apps([]).
|
||||||
|
|
||||||
|
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(_) ->
|
t_get_env(_) ->
|
||||||
?assertEqual(undefined, emqx:get_env(undefined_key)),
|
?assertEqual(undefined, emqx:get_env(undefined_key)),
|
||||||
?assertEqual(default_value, emqx:get_env(undefined_key, default_value)),
|
?assertEqual(default_value, emqx:get_env(undefined_key, default_value)),
|
||||||
|
|
@ -46,18 +58,28 @@ t_emqx_pubsub_api(_) ->
|
||||||
{ok, C} = emqtt:start_link([{host, "localhost"}, {clientid, "myclient"}]),
|
{ok, C} = emqtt:start_link([{host, "localhost"}, {clientid, "myclient"}]),
|
||||||
{ok, _} = emqtt:connect(C),
|
{ok, _} = emqtt:connect(C),
|
||||||
ClientId = <<"myclient">>,
|
ClientId = <<"myclient">>,
|
||||||
Topic = <<"mytopic">>,
|
|
||||||
Payload = <<"Hello World">>,
|
Payload = <<"Hello World">>,
|
||||||
|
Topic = <<"mytopic">>,
|
||||||
Topic1 = <<"mytopic1">>,
|
Topic1 = <<"mytopic1">>,
|
||||||
|
Topic2 = <<"mytopic2">>,
|
||||||
|
Topic3 = <<"mytopic3">>,
|
||||||
emqx:subscribe(Topic, ClientId),
|
emqx:subscribe(Topic, ClientId),
|
||||||
|
emqx:subscribe(Topic1, ClientId, #{qos => 1}),
|
||||||
|
emqx:subscribe(Topic2, ClientId, #{qos => 2}),
|
||||||
ct:sleep(100),
|
ct:sleep(100),
|
||||||
?assertEqual([Topic], emqx:topics()),
|
?assertEqual([Topic2, Topic1, Topic], emqx:topics()),
|
||||||
?assertEqual([self()], emqx:subscribers(Topic)),
|
?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(self(), Topic)),
|
||||||
?assertEqual(true, emqx:subscribed(ClientId, Topic)),
|
?assertEqual(true, emqx:subscribed(ClientId, Topic)),
|
||||||
?assertEqual(false, emqx:subscribed(self(), Topic1)),
|
?assertEqual(true, emqx:subscribed(self(), Topic1)),
|
||||||
?assertEqual(false, emqx:subscribed(ClientId, 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)),
|
emqx:publish(emqx_message:make(Topic, Payload)),
|
||||||
receive
|
receive
|
||||||
{deliver, Topic, #message{payload = Payload}} ->
|
{deliver, Topic, #message{payload = Payload}} ->
|
||||||
|
|
@ -65,26 +87,100 @@ t_emqx_pubsub_api(_) ->
|
||||||
after 100 ->
|
after 100 ->
|
||||||
ct:fail("no_message")
|
ct:fail("no_message")
|
||||||
end,
|
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(Topic),
|
||||||
|
emqx:unsubscribe(Topic1),
|
||||||
|
emqx:unsubscribe(Topic2),
|
||||||
ct:sleep(20),
|
ct:sleep(20),
|
||||||
?assertEqual([], emqx:topics()).
|
?assertEqual([], emqx:topics()).
|
||||||
|
|
||||||
t_emqx_hook_api(_) ->
|
t_hook_unhook(_) ->
|
||||||
InitArgs = ['arg2', 'arg3'],
|
ok = emqx:hook(test_hook, fun ?MODULE:hook_fun1/1, []),
|
||||||
emqx:hook('hook.run', fun run/3, InitArgs),
|
ok = emqx:hook(test_hook, fun ?MODULE:hook_fun2/1, []),
|
||||||
ok = emqx:run_hook('hook.run', ['arg1']),
|
?assertEqual({error, already_exists},
|
||||||
emqx:unhook('hook.run', fun run/3),
|
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),
|
ok = emqx:hook(emqx_hook, {?MODULE, hook_fun8, []}, 8),
|
||||||
emqx:hook('hook.run_fold', fun add2/1),
|
ok = emqx:hook(emqx_hook, {?MODULE, hook_fun2, []}, 2),
|
||||||
4 = emqx:run_fold_hook('hook.run_fold', [], 1),
|
ok = emqx:hook(emqx_hook, {?MODULE, hook_fun10, []}, 10),
|
||||||
emqx:unhook('hook.run_fold', fun add1/1),
|
ok = emqx:hook(emqx_hook, {?MODULE, hook_fun9, []}, 9),
|
||||||
emqx:unhook('hook.run_fold', fun add2/1).
|
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') ->
|
t_run_hook(_) ->
|
||||||
ok;
|
ok = emqx:hook(foldl_hook, fun ?MODULE:hook_fun3/4, [init]),
|
||||||
run(_, _, _) ->
|
ok = emqx:hook(foldl_hook, {?MODULE, hook_fun3, [init]}),
|
||||||
ct:fail("no_match").
|
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}.
|
ok = emqx:hook(foldl_hook2, fun ?MODULE:hook_fun9/2),
|
||||||
add2(N) -> {ok, N + 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.
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqx_oom_SUITE).
|
-module(emqx_access_control_SUITE).
|
||||||
|
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
|
@ -23,22 +23,18 @@
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
t_init(_) ->
|
init_per_testcase(_TestCase, Config) ->
|
||||||
Opts = #{message_queue_len => 10,
|
Config.
|
||||||
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(_) ->
|
end_per_testcase(_TestCase, Config) ->
|
||||||
Opts = #{message_queue_len => 10,
|
Config.
|
||||||
max_heap_size => 1024*1024*8
|
|
||||||
},
|
% t_authenticate(_) ->
|
||||||
Oom = emqx_oom:init(Opts),
|
% error('TODO').
|
||||||
[self() ! {msg, I} || I <- lists:seq(1, 5)],
|
|
||||||
?assertEqual(ok, emqx_oom:check(Oom)),
|
% t_check_acl(_) ->
|
||||||
[self() ! {msg, I} || I <- lists:seq(1, 6)],
|
% error('TODO').
|
||||||
?assertEqual({shutdown, message_queue_too_long}, emqx_oom:check(Oom)).
|
|
||||||
|
% t_reload_acl(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_access_rule_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
% t_compile(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_match(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_acl_cache_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
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_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_k(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_cache_v(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_cleanup_acl_cache(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_get_oldest_key(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_get_newest_key(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_get_cache_max_size(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_get_cache_size(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_dump_acl_cache(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_empty_acl_cache(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_put_acl_cache(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_get_acl_cache(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_is_enabled(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
|
@ -82,8 +82,18 @@ t_alarm_handler(_) ->
|
||||||
|
|
||||||
{ok, ?PUBLISH_PACKET(?QOS_0, Topic2, _, _), <<>>, _} = raw_recv_parse(Data4),
|
{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).
|
end).
|
||||||
|
|
||||||
with_connection(DoFun) ->
|
with_connection(DoFun) ->
|
||||||
|
|
|
||||||
|
|
@ -27,29 +27,10 @@
|
||||||
-include("emqx.hrl").
|
-include("emqx.hrl").
|
||||||
-include("emqx_mqtt.hrl").
|
-include("emqx_mqtt.hrl").
|
||||||
|
|
||||||
all() ->
|
all() -> emqx_ct:all(?MODULE).
|
||||||
[{group, pubsub},
|
|
||||||
{group, metrics},
|
|
||||||
{group, stats}].
|
|
||||||
|
|
||||||
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) ->
|
init_per_suite(Config) ->
|
||||||
emqx_ct_helpers:boot_modules([router, broker]),
|
emqx_ct_helpers:boot_modules(all),
|
||||||
emqx_ct_helpers:start_apps([]),
|
emqx_ct_helpers:start_apps([]),
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
|
|
@ -60,102 +41,173 @@ end_per_suite(_Config) ->
|
||||||
%% PubSub Test
|
%% PubSub Test
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
t_sub_unsub(_) ->
|
t_subscribed(_) ->
|
||||||
ok = emqx_broker:subscribe(<<"topic">>, <<"clientId">>),
|
emqx_broker:subscribe(<<"topic">>),
|
||||||
ok = emqx_broker:subscribe(<<"topic/1">>, <<"clientId">>, #{qos => 1}),
|
?assertEqual(false, emqx_broker:subscribed(undefined, <<"topic">>)),
|
||||||
ok = emqx_broker:subscribe(<<"topic/2">>, <<"clientId">>, #{qos => 2}),
|
?assertEqual(true, emqx_broker:subscribed(self(), <<"topic">>)),
|
||||||
true = emqx_broker:subscribed(<<"clientId">>, <<"topic">>),
|
emqx_broker:unsubscribe(<<"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_publish(_) ->
|
t_subscribed_2(_) ->
|
||||||
Msg = emqx_message:make(ct, <<"test/pubsub">>, <<"hello">>),
|
emqx_broker:subscribe(<<"topic">>, <<"clientid">>),
|
||||||
ok = emqx_broker:subscribe(<<"test/+">>),
|
?assertEqual(true, emqx_broker:subscribed(<<"clientid">>, <<"topic">>)),
|
||||||
timer:sleep(10),
|
?assertEqual(true, emqx_broker:subscribed(self(), <<"topic">>)),
|
||||||
emqx_broker:publish(Msg),
|
emqx_broker:unsubscribe(<<"topic">>).
|
||||||
?assert(receive {deliver, <<"test/+">>, #message{payload = <<"hello">>}} -> true after 100 -> false end).
|
|
||||||
|
|
||||||
t_dispatch_with_no_sub(_) ->
|
t_subopts(_) ->
|
||||||
Msg = emqx_message:make(ct, <<"no_subscribers">>, <<"hello">>),
|
?assertEqual(false, emqx_broker:set_subopts(<<"topic">>, #{qos => 1})),
|
||||||
Delivery = #delivery{sender = self(), message = Msg},
|
?assertEqual(undefined, emqx_broker:get_subopts(self(), <<"topic">>)),
|
||||||
?assertEqual([{node(),<<"no_subscribers">>,{error,no_subscribers}}],
|
?assertEqual(undefined, emqx_broker:get_subopts(<<"clientid">>, <<"topic">>)),
|
||||||
emqx_broker:route([{<<"no_subscribers">>, node()}], Delivery)).
|
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(_) ->
|
t_topics(_) ->
|
||||||
true = emqx:is_running(node()),
|
Topics = [<<"topic">>, <<"topic/1">>, <<"topic/2">>],
|
||||||
Self = self(),
|
ok = emqx_broker:subscribe(lists:nth(1, Topics), <<"clientId">>),
|
||||||
Subscriber = <<"clientId">>,
|
ok = emqx_broker:subscribe(lists:nth(2, Topics), <<"clientId">>),
|
||||||
ok = emqx_broker:subscribe(<<"a/b/c">>, Subscriber, #{ qos => 1 }),
|
ok = emqx_broker:subscribe(lists:nth(3, Topics), <<"clientId">>),
|
||||||
#{qos := 1} = ets:lookup_element(emqx_suboption, {Self, <<"a/b/c">>}, 2),
|
Topics1 = emqx_broker:topics(),
|
||||||
#{qos := 1} = emqx_broker:get_subopts(Subscriber, <<"a/b/c">>),
|
?assertEqual(true, lists:foldl(fun(Topic, Acc) ->
|
||||||
true = emqx_broker:set_subopts(<<"a/b/c">>, #{qos => 0}),
|
case lists:member(Topic, Topics1) of
|
||||||
#{qos := 0} = emqx_broker:get_subopts(Subscriber, <<"a/b/c">>),
|
true -> Acc;
|
||||||
ok = emqx_broker:subscribe(<<"a/b/c">>, Subscriber, #{ qos => 2 }),
|
false -> false
|
||||||
%% ct:log("Emq Sub: ~p.~n", [ets:lookup(emqx_suboption, {<<"a/b/c">>, Subscriber})]),
|
end
|
||||||
timer:sleep(10),
|
end, true, Topics)),
|
||||||
[Self] = emqx_broker:subscribers(<<"a/b/c">>),
|
emqx_broker:unsubscribe(lists:nth(1, Topics)),
|
||||||
emqx_broker:publish(
|
emqx_broker:unsubscribe(lists:nth(2, Topics)),
|
||||||
emqx_message:make(ct, <<"a/b/c">>, <<"hello">>)),
|
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(
|
?assert(
|
||||||
receive {deliver, <<"a/b/c">>, _ } ->
|
receive
|
||||||
|
{deliver, <<"topic">>, #message{payload = <<"hello">>}} ->
|
||||||
true;
|
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 ->
|
after 100 ->
|
||||||
false
|
false
|
||||||
end),
|
end),
|
||||||
spawn(fun() ->
|
emqx_broker:unsubscribe(<<"$share/group/topic">>).
|
||||||
emqx_broker:subscribe(<<"a/b/c">>),
|
|
||||||
emqx_broker:subscribe(<<"c/d/e">>),
|
t_shared_subscribe_2(_) ->
|
||||||
timer:sleep(10),
|
{ok, ConnPid} = emqtt:start_link([{clean_start, true}, {clientid, <<"clientid">>}]),
|
||||||
emqx_broker:unsubscribe(<<"a/b/c">>)
|
{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),
|
end),
|
||||||
timer:sleep(20),
|
ok = meck:unload(emqx_broker_helper).
|
||||||
emqx_broker:unsubscribe(<<"a/b/c">>).
|
|
||||||
|
|
||||||
t_shared_subscribe(_) ->
|
t_stats_fun(_) ->
|
||||||
emqx_broker:subscribe(<<"$share/group2/topic2">>),
|
?assertEqual(0, emqx_stats:getstat('subscribers.count')),
|
||||||
emqx_broker:subscribe(<<"$queue/topic3">>),
|
?assertEqual(0, emqx_stats:getstat('subscriptions.count')),
|
||||||
timer:sleep(10),
|
?assertEqual(0, emqx_stats:getstat('suboptions.count')),
|
||||||
ct:pal("Share subscriptions: ~p",
|
ok = emqx_broker:subscribe(<<"topic">>, <<"clientid">>),
|
||||||
[emqx_broker:subscriptions(self())]),
|
ok = emqx_broker:subscribe(<<"topic2">>, <<"clientid">>),
|
||||||
?assertEqual(2, length(emqx_broker:subscriptions(self()))),
|
emqx_broker:stats_fun(),
|
||||||
emqx_broker:unsubscribe(<<"$share/group2/topic2">>),
|
ct:sleep(10),
|
||||||
emqx_broker:unsubscribe(<<"$queue/topic3">>),
|
?assertEqual(2, emqx_stats:getstat('subscribers.count')),
|
||||||
?assertEqual(0, length(emqx_broker:subscriptions(self()))).
|
?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_pubsub#'(_) ->
|
recv_msgs(Count) ->
|
||||||
emqx_broker:subscribe(<<"a/#">>),
|
recv_msgs(Count, []).
|
||||||
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')).
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_broker_helper_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
emqx_broker_helper:start_link(),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
t_lookup_subid(_) ->
|
||||||
|
?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(_) ->
|
||||||
|
?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(_) ->
|
||||||
|
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">>)).
|
||||||
|
|
@ -19,11 +19,6 @@
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
-import(emqx_channel,
|
|
||||||
[ handle_in/2
|
|
||||||
, handle_out/2
|
|
||||||
]).
|
|
||||||
|
|
||||||
-include("emqx.hrl").
|
-include("emqx.hrl").
|
||||||
-include("emqx_mqtt.hrl").
|
-include("emqx_mqtt.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
@ -45,20 +40,555 @@
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% CT Callbacks
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
emqx_ct_helpers:boot_modules([router, broker]),
|
|
||||||
emqx_ct_helpers:start_apps([]),
|
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
emqx_ct_helpers:stop_apps([]).
|
ok.
|
||||||
|
|
||||||
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
%% CM Meck
|
||||||
|
ok = meck:new(emqx_cm, [passthrough, no_history]),
|
||||||
|
%% Access Control Meck
|
||||||
|
ok = meck:new(emqx_access_control, [passthrough, no_history]),
|
||||||
|
ok = meck:expect(emqx_access_control, authenticate,
|
||||||
|
fun(_) -> {ok, #{auth_result => success}} end),
|
||||||
|
ok = meck:expect(emqx_access_control, check_acl, fun(_, _, _) -> allow end),
|
||||||
|
%% Broker Meck
|
||||||
|
ok = meck:new(emqx_broker, [passthrough, no_history]),
|
||||||
|
%% Hooks Meck
|
||||||
|
ok = meck:new(emqx_hooks, [passthrough, no_history]),
|
||||||
|
ok = meck:expect(emqx_hooks, run, fun(_Hook, _Args) -> ok end),
|
||||||
|
ok = meck:expect(emqx_hooks, run_fold, fun(_Hook, _Args, Acc) -> Acc end),
|
||||||
|
%% Session Meck
|
||||||
|
ok = meck:new(emqx_session, [passthrough, no_history]),
|
||||||
|
%% Metrics
|
||||||
|
ok = meck:new(emqx_metrics, [passthrough, no_history]),
|
||||||
|
ok = meck:expect(emqx_metrics, inc, fun(_) -> ok end),
|
||||||
|
ok = meck:expect(emqx_metrics, inc, fun(_, _) -> ok end),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_testcase(_TestCase, Config) ->
|
||||||
|
ok = meck:unload(emqx_access_control),
|
||||||
|
ok = meck:unload(emqx_metrics),
|
||||||
|
ok = meck:unload(emqx_session),
|
||||||
|
ok = meck:unload(emqx_broker),
|
||||||
|
ok = meck:unload(emqx_hooks),
|
||||||
|
ok = meck:unload(emqx_cm),
|
||||||
|
Config.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Test cases for handle_in
|
%% Test cases for channel info/stats/caps
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
t_handle_connect(_) ->
|
t_chan_info(_) ->
|
||||||
ConnPkt = #mqtt_packet_connect{
|
#{conn_state := connected,
|
||||||
|
clientinfo := ClientInfo
|
||||||
|
} = emqx_channel:info(channel()),
|
||||||
|
?assertEqual(clientinfo(), ClientInfo).
|
||||||
|
|
||||||
|
t_chan_attrs(_) ->
|
||||||
|
#{conn_state := connected} = emqx_channel:attrs(channel()).
|
||||||
|
|
||||||
|
t_chan_caps(_) ->
|
||||||
|
Caps = emqx_channel:caps(channel()).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Test cases for channel init
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%% TODO:
|
||||||
|
t_chan_init(_) ->
|
||||||
|
Channel = channel().
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Test cases for channel handle_in
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
t_handle_in_connect_packet_sucess(_) ->
|
||||||
|
ok = meck:expect(emqx_cm, open_session,
|
||||||
|
fun(true, _ClientInfo, _ConnInfo) ->
|
||||||
|
{ok, #{session => session(), present => false}}
|
||||||
|
end),
|
||||||
|
{ok, [{connack, ?CONNACK_PACKET(?RC_SUCCESS, 0)}], Channel}
|
||||||
|
= emqx_channel:handle_in(?CONNECT_PACKET(connpkt()), channel(#{conn_state => idle})),
|
||||||
|
ClientInfo = emqx_channel:info(clientinfo, Channel),
|
||||||
|
?assertMatch(#{clientid := <<"clientid">>,
|
||||||
|
username := <<"username">>
|
||||||
|
}, ClientInfo),
|
||||||
|
?assertEqual(connected, emqx_channel:info(conn_state, Channel)).
|
||||||
|
|
||||||
|
t_handle_in_unexpected_connect_packet(_) ->
|
||||||
|
Channel = emqx_channel:set_field(conn_state, connected, channel()),
|
||||||
|
{shutdown, protocol_error, ?DISCONNECT_PACKET(?RC_PROTOCOL_ERROR), Channel}
|
||||||
|
= emqx_channel:handle_in(?CONNECT_PACKET(connpkt()), Channel).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
t_handle_in_pubrel_not_found_error(_) ->
|
||||||
|
ok = meck:expect(emqx_session, pubrel,
|
||||||
|
fun(_PacketId, _Session) ->
|
||||||
|
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND}
|
||||||
|
end),
|
||||||
|
{ok, ?PUBCOMP_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), _Channel}
|
||||||
|
= emqx_channel:handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), channel()).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
t_handle_in_pubcomp_not_found_error(_) ->
|
||||||
|
ok = meck:expect(emqx_session, pubcomp,
|
||||||
|
fun(_PacketId, _Session) ->
|
||||||
|
{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)).
|
||||||
|
|
||||||
|
t_handle_in_subscribe(_) ->
|
||||||
|
ok = meck:expect(emqx_session, subscribe,
|
||||||
|
fun(_, _, _, Session) ->
|
||||||
|
{ok, Session}
|
||||||
|
end),
|
||||||
|
Channel = channel(#{conn_state => connected}),
|
||||||
|
TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS}],
|
||||||
|
Subscribe = ?SUBSCRIBE_PACKET(1, #{}, TopicFilters),
|
||||||
|
{ok, ?SUBACK_PACKET(1, [?QOS_0]), _} = emqx_channel:handle_in(Subscribe, Channel).
|
||||||
|
|
||||||
|
t_handle_in_unsubscribe(_) ->
|
||||||
|
ok = meck:expect(emqx_session, unsubscribe,
|
||||||
|
fun(_, _, Session) ->
|
||||||
|
{ok, Session}
|
||||||
|
end),
|
||||||
|
Channel = channel(#{conn_state => connected}),
|
||||||
|
UnsubPkt = ?UNSUBSCRIBE_PACKET(1, #{}, [<<"+">>]),
|
||||||
|
{ok, ?UNSUBACK_PACKET(1), _} = emqx_channel:handle_in(UnsubPkt, Channel).
|
||||||
|
|
||||||
|
t_handle_in_pingreq(_) ->
|
||||||
|
{ok, ?PACKET(?PINGRESP), _Channel}
|
||||||
|
= emqx_channel:handle_in(?PACKET(?PINGREQ), channel()).
|
||||||
|
|
||||||
|
t_handle_in_disconnect(_) ->
|
||||||
|
Channel = channel(#{conn_state => connected}),
|
||||||
|
{shutdown, normal, Channel1} = emqx_channel:handle_in(?DISCONNECT_PACKET(?RC_SUCCESS), Channel),
|
||||||
|
?assertEqual(undefined, emqx_channel:info(will_msg, Channel1)).
|
||||||
|
|
||||||
|
t_handle_in_auth(_) ->
|
||||||
|
Channel = channel(#{conn_state => connected}),
|
||||||
|
Packet = ?DISCONNECT_PACKET(?RC_IMPLEMENTATION_SPECIFIC_ERROR),
|
||||||
|
{shutdown, implementation_specific_error, Packet, Channel}
|
||||||
|
= emqx_channel:handle_in(?AUTH_PACKET(), Channel).
|
||||||
|
|
||||||
|
t_handle_in_frame_error(_) ->
|
||||||
|
IdleChannel = channel(#{conn_state => idle}),
|
||||||
|
{shutdown, frame_too_large, _}
|
||||||
|
= emqx_channel:handle_in({frame_error, frame_too_large}, IdleChannel),
|
||||||
|
ConnectingChan = channel(#{conn_state => connecting}),
|
||||||
|
{shutdown, frame_too_large, ?CONNACK_PACKET(?RC_MALFORMED_PACKET), _}
|
||||||
|
= emqx_channel:handle_in({frame_error, frame_too_large}, ConnectingChan),
|
||||||
|
ConnectedChan = channel(#{conn_state => connected}),
|
||||||
|
{shutdown, malformed_Packet, ?DISCONNECT_PACKET(?RC_MALFORMED_PACKET), _}
|
||||||
|
= emqx_channel:handle_in({frame_error, frame_too_large}, ConnectedChan),
|
||||||
|
DisconnectedChan = channel(#{conn_state => disconnected}),
|
||||||
|
{ok, DisconnectedChan}
|
||||||
|
= emqx_channel:handle_in({frame_error, frame_too_large}, DisconnectedChan).
|
||||||
|
|
||||||
|
%% TODO:
|
||||||
|
t_handle_in_expected_packet(_) ->
|
||||||
|
{shutdown, protocol_error, ?DISCONNECT_PACKET(?RC_PROTOCOL_ERROR), _Chan}
|
||||||
|
= emqx_channel:handle_in(packet, channel()).
|
||||||
|
|
||||||
|
t_process_connect(_) ->
|
||||||
|
ok = meck:expect(emqx_cm, open_session,
|
||||||
|
fun(true, _ClientInfo, _ConnInfo) ->
|
||||||
|
{ok, #{session => session(), present => false}}
|
||||||
|
end),
|
||||||
|
{ok, [{connack, ?CONNACK_PACKET(?RC_SUCCESS)}], _Channel}
|
||||||
|
= emqx_channel:process_connect(connpkt(), channel(#{conn_state => idle})).
|
||||||
|
|
||||||
|
t_handle_publish_qos0(_) ->
|
||||||
|
ok = meck:expect(emqx_broker, publish, fun(_) -> [] end),
|
||||||
|
Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>),
|
||||||
|
{ok, _Channel} = emqx_channel:handle_publish(Publish, channel()).
|
||||||
|
|
||||||
|
t_process_publish_qos1(_) ->
|
||||||
|
ok = meck:expect(emqx_broker, publish, fun(_) -> [] end),
|
||||||
|
Msg = emqx_message:make(test, ?QOS_1, <<"t">>, <<"payload">>),
|
||||||
|
{ok, ?PUBACK_PACKET(1, ?RC_NO_MATCHING_SUBSCRIBERS), _Channel}
|
||||||
|
= emqx_channel:process_publish(1, Msg, channel()).
|
||||||
|
|
||||||
|
t_process_subscribe(_) ->
|
||||||
|
ok = meck:expect(emqx_session, subscribe, fun(_, _, _, Session) -> {ok, Session} end),
|
||||||
|
TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS}],
|
||||||
|
{[?RC_SUCCESS], _Channel} = emqx_channel:process_subscribe(TopicFilters, channel()).
|
||||||
|
|
||||||
|
t_process_unsubscribe(_) ->
|
||||||
|
ok = meck:expect(emqx_session, unsubscribe, fun(_, _, Session) -> {ok, Session} end),
|
||||||
|
TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS}],
|
||||||
|
{[?RC_SUCCESS], _Channel} = emqx_channel:process_unsubscribe(TopicFilters, channel()).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Test cases for handle_out
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
t_handle_out_delivers(_) ->
|
||||||
|
WithPacketId = fun(Msgs) ->
|
||||||
|
lists:zip(lists:seq(1, length(Msgs)), Msgs)
|
||||||
|
end,
|
||||||
|
ok = meck:expect(emqx_session, deliver,
|
||||||
|
fun(Delivers, Session) ->
|
||||||
|
Msgs = [Msg || {deliver, _, Msg} <- Delivers],
|
||||||
|
Publishes = [{publish, PacketId, Msg}
|
||||||
|
|| {PacketId, Msg} <- WithPacketId(Msgs)],
|
||||||
|
{ok, Publishes, Session}
|
||||||
|
end),
|
||||||
|
ok = meck:expect(emqx_session, info, fun(retry_interval, _Session) -> 20000 end),
|
||||||
|
Msg0 = emqx_message:make(test, ?QOS_1, <<"t1">>, <<"qos1">>),
|
||||||
|
Msg1 = emqx_message:make(test, ?QOS_2, <<"t2">>, <<"qos2">>),
|
||||||
|
Delivers = [{deliver, <<"+">>, Msg0}, {deliver, <<"+">>, Msg1}],
|
||||||
|
{ok, {outgoing, Packets}, _Ch} = emqx_channel:handle_out(Delivers, channel()),
|
||||||
|
?assertEqual([?QOS_1, ?QOS_2], [emqx_packet:qos(Pkt)|| Pkt <- Packets]).
|
||||||
|
|
||||||
|
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}
|
||||||
|
= emqx_channel:handle_out({publish, [Pub0, Pub1]}, Channel),
|
||||||
|
?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">>),
|
||||||
|
{ok, ?PUBLISH_PACKET(?QOS_1, <<"t">>, 1, <<"payload">>), _Chan}
|
||||||
|
= emqx_channel:handle_out({publish, 1, Msg}, channel()).
|
||||||
|
|
||||||
|
t_handle_out_publish_nl(_) ->
|
||||||
|
ClientInfo = clientinfo(#{clientid => <<"clientid">>}),
|
||||||
|
Channel = channel(#{clientinfo => ClientInfo}),
|
||||||
|
Msg = emqx_message:make(<<"clientid">>, ?QOS_1, <<"t1">>, <<"qos1">>),
|
||||||
|
Publish = {publish, 1, emqx_message:set_flag(nl, Msg)},
|
||||||
|
{ok, Channel} = emqx_channel:handle_out(Publish, Channel).
|
||||||
|
|
||||||
|
t_handle_out_connack_sucess(_) ->
|
||||||
|
{ok, [{connack, ?CONNACK_PACKET(?RC_SUCCESS, SP, _)}], _Chan}
|
||||||
|
= emqx_channel:handle_out(connack, {?RC_SUCCESS, 0, connpkt()}, channel()).
|
||||||
|
|
||||||
|
t_handle_out_connack_failure(_) ->
|
||||||
|
{shutdown, not_authorized, ?CONNACK_PACKET(?RC_NOT_AUTHORIZED), _Chan}
|
||||||
|
= emqx_channel:handle_out(connack, {?RC_NOT_AUTHORIZED, connpkt()}, channel()).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
||||||
|
t_handle_out_suback(_) ->
|
||||||
|
{ok, ?SUBACK_PACKET(1, [?QOS_2]), _Channel}
|
||||||
|
= emqx_channel:handle_out(suback, {1, [?QOS_2]}, channel()).
|
||||||
|
|
||||||
|
t_handle_out_unsuback(_) ->
|
||||||
|
{ok, ?UNSUBACK_PACKET(1, [?RC_SUCCESS]), _Channel}
|
||||||
|
= emqx_channel:handle_out(unsuback, {1, [?RC_SUCCESS]}, channel()).
|
||||||
|
|
||||||
|
t_handle_out_disconnect(_) ->
|
||||||
|
{shutdown, normal, ?DISCONNECT_PACKET(?RC_SUCCESS), _Chan}
|
||||||
|
= emqx_channel:handle_out(disconnect, ?RC_SUCCESS, channel()).
|
||||||
|
|
||||||
|
t_handle_out_unexpected(_) ->
|
||||||
|
{ok, _Channel} = emqx_channel:handle_out(unexpected, <<"data">>, channel()).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Test cases for handle_call
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
t_handle_call_kick(_) ->
|
||||||
|
{shutdown, kicked, ok, _Chan} = emqx_channel:handle_call(kick, channel()).
|
||||||
|
|
||||||
|
t_handle_call_discard(_) ->
|
||||||
|
Packet = ?DISCONNECT_PACKET(?RC_SESSION_TAKEN_OVER),
|
||||||
|
{shutdown, discarded, ok, Packet, _Channel}
|
||||||
|
= emqx_channel:handle_call(discard, channel()).
|
||||||
|
|
||||||
|
t_handle_call_takeover_begin(_) ->
|
||||||
|
{reply, undefined, _Channel}
|
||||||
|
= emqx_channel:handle_call({takeover, 'begin'}, channel()).
|
||||||
|
|
||||||
|
t_handle_call_takeover_end(_) ->
|
||||||
|
ok = meck:expect(emqx_session, takeover, fun(_) -> ok end),
|
||||||
|
{shutdown, takeovered, [], _Channel}
|
||||||
|
= emqx_channel:handle_call({takeover, 'end'}, channel()).
|
||||||
|
|
||||||
|
t_handle_call_unexpected(_) ->
|
||||||
|
{reply, ignored, _Channel} = emqx_channel:handle_call(unexpected_req, channel()).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Test cases for handle_info
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
t_handle_info_subscribe(_) ->
|
||||||
|
ok = meck:expect(emqx_session, subscribe, fun(_, _, _, Session) -> {ok, Session} end),
|
||||||
|
{ok, _Chan} = emqx_channel:handle_info({subscribe, topic_filters()}, channel()).
|
||||||
|
|
||||||
|
t_handle_info_unsubscribe(_) ->
|
||||||
|
ok = meck:expect(emqx_session, unsubscribe, fun(_, _, Session) -> {ok, Session} end),
|
||||||
|
{ok, _Chan} = emqx_channel:handle_info({unsubscribe, topic_filters()}, channel()).
|
||||||
|
|
||||||
|
t_handle_info_sock_closed(_) ->
|
||||||
|
{ok, _Chan} = emqx_channel:handle_out({sock_closed, reason},
|
||||||
|
channel(#{conn_state => disconnected})).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Test cases for handle_timeout
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
t_handle_timeout_emit_stats(_) ->
|
||||||
|
ok = meck:expect(emqx_cm, set_chan_stats, fun(_, _) -> ok end),
|
||||||
|
TRef = make_ref(),
|
||||||
|
Channel = emqx_channel:set_field(timers, #{stats_timer => TRef}, channel()),
|
||||||
|
{ok, _Chan} = emqx_channel:handle_timeout(TRef, {emit_stats, []}, Channel).
|
||||||
|
|
||||||
|
t_handle_timeout_keepalive(_) ->
|
||||||
|
TRef = make_ref(),
|
||||||
|
Channel = emqx_channel:set_field(timers, #{alive_timer => TRef}, channel()),
|
||||||
|
{ok, _Chan} = emqx_channel:handle_timeout(make_ref(), {keepalive, 10}, channel()).
|
||||||
|
|
||||||
|
t_handle_timeout_retry_delivery(_) ->
|
||||||
|
ok = meck:expect(emqx_session, retry, fun(Session) -> {ok, Session} end),
|
||||||
|
TRef = make_ref(),
|
||||||
|
Channel = emqx_channel:set_field(timers, #{retry_timer => TRef}, channel()),
|
||||||
|
{ok, _Chan} = emqx_channel:handle_timeout(TRef, retry_delivery, channel()).
|
||||||
|
|
||||||
|
t_handle_timeout_expire_awaiting_rel(_) ->
|
||||||
|
ok = meck:expect(emqx_session, expire, fun(_, Session) -> {ok, Session} end),
|
||||||
|
TRef = make_ref(),
|
||||||
|
Channel = emqx_channel:set_field(timers, #{await_timer => TRef}, channel()),
|
||||||
|
{ok, _Chan} = emqx_channel:handle_timeout(TRef, expire_awaiting_rel, Channel).
|
||||||
|
|
||||||
|
t_handle_timeout_expire_session(_) ->
|
||||||
|
TRef = make_ref(),
|
||||||
|
Channel = emqx_channel:set_field(timers, #{expire_timer => TRef}, channel()),
|
||||||
|
{shutdown, expired, _Chan} = emqx_channel:handle_timeout(TRef, expire_session, Channel).
|
||||||
|
|
||||||
|
t_handle_timeout_will_message(_) ->
|
||||||
|
{ok, _Chan} = emqx_channel:handle_timeout(make_ref(), will_message, channel()).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Test cases for internal functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
t_enrich_conninfo(_) ->
|
||||||
|
{ok, _Chan} = emqx_channel:enrich_conninfo(connpkt(), channel()).
|
||||||
|
|
||||||
|
t_enrich_client(_) ->
|
||||||
|
{ok, _ConnPkt, _Chan} = emqx_channel:enrich_client(connpkt(), channel()).
|
||||||
|
|
||||||
|
t_check_banned(_) ->
|
||||||
|
ok = emqx_channel:check_banned(connpkt(), channel()).
|
||||||
|
|
||||||
|
t_check_flapping(_) ->
|
||||||
|
ok = emqx_channel:check_flapping(connpkt(), channel()).
|
||||||
|
|
||||||
|
t_auth_connect(_) ->
|
||||||
|
{ok, _Chan} = emqx_channel:auth_connect(connpkt(), channel()).
|
||||||
|
|
||||||
|
t_process_alias(_) ->
|
||||||
|
Publish = #mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias' => 1}},
|
||||||
|
Channel = emqx_channel:set_field(topic_aliases, #{1 => <<"t">>}, channel()),
|
||||||
|
{ok, #mqtt_packet{variable = #mqtt_packet_publish{topic_name = <<"t">>}}, _Chan}
|
||||||
|
= emqx_channel:process_alias(#mqtt_packet{variable = Publish}, Channel).
|
||||||
|
|
||||||
|
t_check_pub_acl(_) ->
|
||||||
|
ok = meck:new(emqx_zone, [passthrough, no_history]),
|
||||||
|
ok = meck:expect(emqx_zone, enable_acl, fun(_) -> true end),
|
||||||
|
Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>),
|
||||||
|
ok = emqx_channel:check_pub_acl(Publish, channel()),
|
||||||
|
ok = meck:unload(emqx_zone).
|
||||||
|
|
||||||
|
t_check_pub_alias(_) ->
|
||||||
|
Publish = #mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias' => 1}},
|
||||||
|
Channel = emqx_channel:set_field(alias_maximum, #{inbound => 10}, channel()),
|
||||||
|
ok = emqx_channel:check_pub_alias(#mqtt_packet{variable = Publish}, Channel).
|
||||||
|
|
||||||
|
t_check_subscribe(_) ->
|
||||||
|
ok = meck:new(emqx_zone, [passthrough, no_history]),
|
||||||
|
ok = meck:expect(emqx_zone, enable_acl, fun(_) -> true end),
|
||||||
|
ok = emqx_channel:check_subscribe(<<"t">>, ?DEFAULT_SUBOPTS, channel()),
|
||||||
|
ok = meck:unload(emqx_zone).
|
||||||
|
|
||||||
|
t_enrich_caps(_) ->
|
||||||
|
ok = meck:new(emqx_mqtt_caps, [passthrough, no_history]),
|
||||||
|
ok = meck:expect(emqx_mqtt_caps, get_caps,
|
||||||
|
fun(_Zone) ->
|
||||||
|
#{max_packet_size => 1024,
|
||||||
|
max_qos_allowed => ?QOS_2,
|
||||||
|
retain_available => true,
|
||||||
|
max_topic_alias => 10,
|
||||||
|
shared_subscription => true,
|
||||||
|
wildcard_subscription => true
|
||||||
|
}
|
||||||
|
end),
|
||||||
|
AckProps = emqx_channel:enrich_caps(#{}, channel()),
|
||||||
|
?assertMatch(#{'Retain-Available' := 1,
|
||||||
|
'Maximum-Packet-Size' := 1024,
|
||||||
|
'Topic-Alias-Maximum' := 10,
|
||||||
|
'Wildcard-Subscription-Available' := 1,
|
||||||
|
'Subscription-Identifier-Available' := 1,
|
||||||
|
'Shared-Subscription-Available' := 1,
|
||||||
|
'Maximum-QoS' := ?QOS_2
|
||||||
|
}, AckProps),
|
||||||
|
ok = meck:unload(emqx_mqtt_caps).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Test cases for terminate
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
t_terminate(_) ->
|
||||||
|
ok = emqx_channel:terminate(normal, channel()),
|
||||||
|
ok = emqx_channel:terminate(sock_error, channel(#{conn_state => connected})),
|
||||||
|
ok = emqx_channel:terminate({shutdown, kicked}, channel(#{conn_state => connected})).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Helper functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
channel() -> channel(#{}).
|
||||||
|
channel(InitFields) ->
|
||||||
|
maps:fold(fun(Field, Value, Channel) ->
|
||||||
|
emqx_channel:set_field(Field, Value, Channel)
|
||||||
|
end, default_channel(), InitFields).
|
||||||
|
|
||||||
|
default_channel() ->
|
||||||
|
Channel = emqx_channel:init(?DEFAULT_CONNINFO, [{zone, zone}]),
|
||||||
|
Channel1 = emqx_channel:set_field(conn_state, connected, Channel),
|
||||||
|
emqx_channel:set_field(clientinfo, clientinfo(), Channel1).
|
||||||
|
|
||||||
|
clientinfo() -> clientinfo(#{}).
|
||||||
|
clientinfo(InitProps) ->
|
||||||
|
maps:merge(#{zone => zone,
|
||||||
|
protocol => mqtt,
|
||||||
|
peerhost => {127,0,0,1},
|
||||||
|
clientid => <<"clientid">>,
|
||||||
|
username => <<"username">>,
|
||||||
|
is_superuser => false,
|
||||||
|
peercert => undefined,
|
||||||
|
mountpoint => undefined
|
||||||
|
}, InitProps).
|
||||||
|
|
||||||
|
topic_filters() ->
|
||||||
|
[{<<"+">>, ?DEFAULT_SUBOPTS}, {<<"#">>, ?DEFAULT_SUBOPTS}].
|
||||||
|
|
||||||
|
connpkt() ->
|
||||||
|
#mqtt_packet_connect{
|
||||||
proto_name = <<"MQTT">>,
|
proto_name = <<"MQTT">>,
|
||||||
proto_ver = ?MQTT_PROTO_V4,
|
proto_ver = ?MQTT_PROTO_V4,
|
||||||
is_bridge = false,
|
is_bridge = false,
|
||||||
|
|
@ -68,265 +598,13 @@ t_handle_connect(_) ->
|
||||||
clientid = <<"clientid">>,
|
clientid = <<"clientid">>,
|
||||||
username = <<"username">>,
|
username = <<"username">>,
|
||||||
password = <<"passwd">>
|
password = <<"passwd">>
|
||||||
},
|
}.
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
ConnAck = ?CONNACK_PACKET(?RC_SUCCESS, 0, #{}),
|
|
||||||
ExpectedOutput = [{enter, connected},{outgoing, ConnAck}],
|
|
||||||
{ok, Output, Channel1} = handle_in(?CONNECT_PACKET(ConnPkt), Channel),
|
|
||||||
?assertEqual(ExpectedOutput, Output),
|
|
||||||
#{clientid := ClientId, username := Username} = emqx_channel:info(clientinfo, Channel1),
|
|
||||||
?assertEqual(<<"clientid">>, ClientId),
|
|
||||||
?assertEqual(<<"username">>, Username)
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_in_publish_qos0(_) ->
|
session() -> session(#{}).
|
||||||
with_channel(
|
session(InitFields) when is_map(InitFields) ->
|
||||||
fun(Channel) ->
|
maps:fold(fun(Field, Value, Session) ->
|
||||||
Publish = ?PUBLISH_PACKET(?QOS_0, <<"topic">>, undefined, <<"payload">>),
|
emqx_session:set_field(Field, Value, Session)
|
||||||
{ok, Channel1} = handle_in(Publish, Channel),
|
end,
|
||||||
?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, Channel1))
|
emqx_session:init(#{zone => zone}, #{receive_maximum => 0}),
|
||||||
end).
|
InitFields).
|
||||||
|
|
||||||
t_handle_in_publish_qos1(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
Publish = ?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, <<"payload">>),
|
|
||||||
{ok, ?PUBACK_PACKET(1, RC), _} = handle_in(Publish, Channel),
|
|
||||||
?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS))
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_publish_qos2(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
Publish1 = ?PUBLISH_PACKET(?QOS_2, <<"topic">>, 1, <<"payload">>),
|
|
||||||
{ok, ?PUBREC_PACKET(1, RC), Channel1} = handle_in(Publish1, Channel),
|
|
||||||
Publish2 = ?PUBLISH_PACKET(?QOS_2, <<"topic">>, 2, <<"payload">>),
|
|
||||||
{ok, ?PUBREC_PACKET(2, RC), Channel2} = handle_in(Publish2, Channel1),
|
|
||||||
?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)),
|
|
||||||
#{awaiting_rel := AwaitingRel} = emqx_channel:info(session, Channel2),
|
|
||||||
?assertEqual(2, AwaitingRel)
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_in_puback(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{ok, Channel1} = handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), Channel),
|
|
||||||
?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel1))
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_in_pubrec(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel1}
|
|
||||||
= handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel),
|
|
||||||
?assertEqual(#{pubrec_in => 1, pubrel_out => 1}, emqx_channel:info(pub_stats, Channel1))
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_in_pubrel(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{ok, ?PUBCOMP_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel1}
|
|
||||||
= handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel),
|
|
||||||
?assertEqual(#{pubrel_in => 1, pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel1))
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_in_pubcomp(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{ok, Channel1} = handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel),
|
|
||||||
?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel1))
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_subscribe(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS}],
|
|
||||||
{ok, ?SUBACK_PACKET(10, [?QOS_0]), Channel1}
|
|
||||||
= handle_in(?SUBSCRIBE_PACKET(10, #{}, TopicFilters), Channel),
|
|
||||||
#{subscriptions := Subscriptions}
|
|
||||||
= emqx_channel:info(session, Channel1),
|
|
||||||
?assertEqual(maps:from_list(TopicFilters), Subscriptions)
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_unsubscribe(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{ok, ?UNSUBACK_PACKET(11), Channel}
|
|
||||||
= handle_in(?UNSUBSCRIBE_PACKET(11, #{}, [<<"+">>]), Channel)
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_pingreq(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{ok, ?PACKET(?PINGRESP), Channel} = handle_in(?PACKET(?PINGREQ), Channel)
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_disconnect(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{stop, {shutdown, normal}, Channel1} = handle_in(?DISCONNECT_PACKET(?RC_SUCCESS), Channel),
|
|
||||||
?assertEqual(undefined, emqx_channel:info(will_msg, Channel1))
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_in_auth(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
Packet = ?DISCONNECT_PACKET(?RC_IMPLEMENTATION_SPECIFIC_ERROR),
|
|
||||||
{stop, {shutdown, implementation_specific_error}, Packet, Channel} = handle_in(?AUTH_PACKET(), Channel)
|
|
||||||
end).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Test cases for handle_deliver
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
t_handle_deliver(_) ->
|
|
||||||
with_connected_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS#{qos => ?QOS_2}}],
|
|
||||||
{ok, ?SUBACK_PACKET(1, [?QOS_2]), Channel1}
|
|
||||||
= handle_in(?SUBSCRIBE_PACKET(1, #{}, TopicFilters), Channel),
|
|
||||||
Msg0 = emqx_message:make(<<"clientx">>, ?QOS_0, <<"t0">>, <<"qos0">>),
|
|
||||||
Msg1 = emqx_message:make(<<"clientx">>, ?QOS_1, <<"t1">>, <<"qos1">>),
|
|
||||||
Delivers = [{deliver, <<"+">>, Msg0}, {deliver, <<"+">>, Msg1}],
|
|
||||||
{ok, {outgoing, Packets}, _Ch} = emqx_channel:handle_out(Delivers, Channel1),
|
|
||||||
?assertEqual([?QOS_0, ?QOS_1], [emqx_packet:qos(Pkt)|| Pkt <- Packets])
|
|
||||||
end).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Test cases for handle_out
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
t_handle_out_connack(_) ->
|
|
||||||
ConnPkt = #mqtt_packet_connect{
|
|
||||||
proto_name = <<"MQTT">>,
|
|
||||||
proto_ver = ?MQTT_PROTO_V4,
|
|
||||||
clean_start = true,
|
|
||||||
properties = #{},
|
|
||||||
clientid = <<"clientid">>
|
|
||||||
},
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{ok, [{enter, connected},{outgoing, ?CONNACK_PACKET(?RC_SUCCESS, SP, _)}], _Chan}
|
|
||||||
= handle_out({connack, ?RC_SUCCESS, 0, ConnPkt}, Channel),
|
|
||||||
{stop, {shutdown, not_authorized}, ?CONNACK_PACKET(?RC_NOT_AUTHORIZED), _}
|
|
||||||
= handle_out({connack, ?RC_NOT_AUTHORIZED, ConnPkt}, Channel)
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_out_publish(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
Pub0 = {publish, undefined, emqx_message:make(<<"t">>, <<"qos0">>)},
|
|
||||||
Pub1 = {publish, 1, emqx_message:make(<<"c">>, ?QOS_1, <<"t">>, <<"qos1">>)},
|
|
||||||
{ok, ?PUBLISH_PACKET(?QOS_0), Channel} = handle_out(Pub0, Channel),
|
|
||||||
{ok, ?PUBLISH_PACKET(?QOS_1), Channel} = handle_out(Pub1, Channel),
|
|
||||||
{ok, {outgoing, Packets}, Channel1} = handle_out({publish, [Pub0, Pub1]}, Channel),
|
|
||||||
?assertEqual(2, length(Packets)),
|
|
||||||
?assertEqual(#{publish_out => 2}, emqx_channel:info(pub_stats, Channel1))
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_out_puback(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{ok, Channel} = handle_out({puberr, ?RC_NOT_AUTHORIZED}, Channel),
|
|
||||||
{ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), Channel1}
|
|
||||||
= handle_out({puback, 1, ?RC_SUCCESS}, Channel),
|
|
||||||
?assertEqual(#{puback_out => 1}, emqx_channel:info(pub_stats, Channel1))
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_out_pubrec(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{ok, ?PUBREC_PACKET(4, ?RC_SUCCESS), Channel1}
|
|
||||||
= handle_out({pubrec, 4, ?RC_SUCCESS}, Channel),
|
|
||||||
?assertEqual(#{pubrec_out => 1}, emqx_channel:info(pub_stats, Channel1))
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_out_pubrel(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{ok, ?PUBREL_PACKET(2), Channel1}
|
|
||||||
= handle_out({pubrel, 2, ?RC_SUCCESS}, Channel),
|
|
||||||
{ok, ?PUBREL_PACKET(3, ?RC_SUCCESS), Channel2}
|
|
||||||
= handle_out({pubrel, 3, ?RC_SUCCESS}, Channel1),
|
|
||||||
?assertEqual(#{pubrel_out => 2}, emqx_channel:info(pub_stats, Channel2))
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_out_pubcomp(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{ok, ?PUBCOMP_PACKET(5, ?RC_SUCCESS), Channel1}
|
|
||||||
= handle_out({pubcomp, 5, ?RC_SUCCESS}, Channel),
|
|
||||||
?assertEqual(#{pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel1))
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_out_suback(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{ok, ?SUBACK_PACKET(1, [?QOS_2]), Channel}
|
|
||||||
= handle_out({suback, 1, [?QOS_2]}, Channel)
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_out_unsuback(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
{ok, ?UNSUBACK_PACKET(1), Channel}
|
|
||||||
= handle_out({unsuback, 1, [?RC_SUCCESS]}, Channel)
|
|
||||||
end).
|
|
||||||
|
|
||||||
t_handle_out_disconnect(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
handle_out({disconnect, ?RC_SUCCESS}, Channel)
|
|
||||||
end).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Test cases for handle_timeout
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
t_handle_timeout(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
'TODO'
|
|
||||||
end).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Test cases for terminate
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
t_terminate(_) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
'TODO'
|
|
||||||
end).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Helper functions
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
with_connected_channel(TestFun) ->
|
|
||||||
with_channel(
|
|
||||||
fun(Channel) ->
|
|
||||||
TestFun(emqx_channel:set_field(conn_state, connected, Channel))
|
|
||||||
end).
|
|
||||||
|
|
||||||
with_channel(TestFun) ->
|
|
||||||
with_channel(#{}, TestFun).
|
|
||||||
|
|
||||||
with_channel(ConnInfo, TestFun) ->
|
|
||||||
ConnInfo1 = maps:merge(?DEFAULT_CONNINFO, ConnInfo),
|
|
||||||
ClientInfo = #{zone => <<"external">>,
|
|
||||||
protocol => mqtt,
|
|
||||||
peerhost => {127,0,0,1},
|
|
||||||
clientid => <<"clientid">>,
|
|
||||||
username => <<"username">>,
|
|
||||||
peercert => undefined,
|
|
||||||
is_bridge => false,
|
|
||||||
is_superuser => false,
|
|
||||||
mountpoint => undefined
|
|
||||||
},
|
|
||||||
Channel = emqx_channel:init(ConnInfo1, [{zone, testing}]),
|
|
||||||
Session = emqx_session:init(ClientInfo, ConnInfo1),
|
|
||||||
Channel1 = emqx_channel:set_field(clientinfo, ClientInfo, Channel),
|
|
||||||
TestFun(emqx_channel:set_field(session, Session, Channel1)).
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,3 +88,27 @@ t_lock_clientid(_) ->
|
||||||
{true, _Nodes} = emqx_cm_locker:unlock(<<"clientid">>),
|
{true, _Nodes} = emqx_cm_locker:unlock(<<"clientid">>),
|
||||||
{true, _Nodes} = emqx_cm_locker:unlock(<<"clientid">>).
|
{true, _Nodes} = emqx_cm_locker:unlock(<<"clientid">>).
|
||||||
|
|
||||||
|
|
||||||
|
% t_unregister_channel(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_get_chan_attrs(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_get_chan_stats(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_lookup_channels(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_set_chan_stats(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_set_chan_attrs(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_register_channel(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_stats_fun(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_cm_locker_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
% t_start_link(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_trans(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_lock(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_unlock(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_cm_registry_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-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_is_enabled(_) ->
|
||||||
|
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_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)),
|
||||||
|
|
||||||
|
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)).
|
||||||
|
|
@ -19,40 +19,336 @@
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include("emqx_mqtt.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
-define(STATS_KYES, [recv_pkt, recv_msg, send_pkt, send_msg,
|
||||||
|
recv_oct, recv_cnt, send_oct, send_cnt,
|
||||||
|
send_pend
|
||||||
|
]).
|
||||||
|
|
||||||
|
all() -> emqx_ct:all(?MODULE) ++ [{group, real_client}].
|
||||||
|
|
||||||
|
groups() ->
|
||||||
|
[{real_client, [non_parallel_tests],
|
||||||
|
[
|
||||||
|
g_get_conn_stats,
|
||||||
|
g_handle_sock_passive
|
||||||
|
]}].
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% CT callbacks
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
emqx_ct_helpers:boot_modules(all),
|
|
||||||
emqx_ct_helpers:start_apps([]),
|
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
emqx_ct_helpers:stop_apps([]).
|
ok.
|
||||||
|
|
||||||
t_basic(_) ->
|
init_per_group(real_client, Config) ->
|
||||||
Topic = <<"TopicA">>,
|
emqx_ct_helpers:boot_modules(all),
|
||||||
{ok, C} = emqtt:start_link([{port, 1883}, {clientid, <<"hello">>}]),
|
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]),
|
||||||
|
ok = meck:expect(emqx_transport, wait, fun(Sock) -> {ok, Sock} end),
|
||||||
|
ok = meck:expect(emqx_transport, type, fun(_Sock) -> tcp end),
|
||||||
|
ok = meck:expect(emqx_transport, ensure_ok_or_exit,
|
||||||
|
fun(peername, [sock]) -> {ok, {{127,0,0,1}, 3456}};
|
||||||
|
(sockname, [sock]) -> {ok, {{127,0,0,1}, 1883}};
|
||||||
|
(peercert, [sock]) -> undefined
|
||||||
|
end),
|
||||||
|
ok = meck:expect(emqx_transport, setopts, fun(_Sock, _Opts) -> ok end),
|
||||||
|
ok = meck:expect(emqx_transport, getstat, fun(_Sock, Options) ->
|
||||||
|
{ok, [{K, 0} || K <- Options]}
|
||||||
|
end),
|
||||||
|
ok = meck:expect(emqx_transport, async_send, fun(_Sock, _Data) -> ok end),
|
||||||
|
ok = meck:expect(emqx_transport, fast_close, fun(_Sock) -> ok end),
|
||||||
|
%% Meck Channel
|
||||||
|
ok = meck:new(emqx_channel, [passthrough, no_history]),
|
||||||
|
%% Meck Metrics
|
||||||
|
ok = meck:new(emqx_metrics, [passthrough, no_history]),
|
||||||
|
ok = meck:expect(emqx_metrics, inc, fun(_, _) -> ok end),
|
||||||
|
ok = meck:expect(emqx_metrics, inc_recv, fun(_) -> ok end),
|
||||||
|
ok = meck:expect(emqx_metrics, inc_sent, fun(_) -> ok end),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_testcase(_TestCase, Config) ->
|
||||||
|
ok = meck:unload(emqx_transport),
|
||||||
|
ok = meck:unload(emqx_channel),
|
||||||
|
ok = meck:unload(emqx_metrics),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Test cases
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
t_start_link_ok(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
state = element(1, sys:get_state(CPid))
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_start_link_exit_on_wait(_) ->
|
||||||
|
ok = exit_on_wait_error(enotconn, normal),
|
||||||
|
ok = exit_on_wait_error(einval, normal),
|
||||||
|
ok = exit_on_wait_error(closed, normal),
|
||||||
|
ok = exit_on_wait_error(timeout, {shutdown, ssl_upgrade_timeout}),
|
||||||
|
ok = exit_on_wait_error(enetdown, {shutdown, enetdown}).
|
||||||
|
|
||||||
|
t_start_link_exit_on_activate(_) ->
|
||||||
|
ok = exit_on_activate_error(enotconn, normal),
|
||||||
|
ok = exit_on_activate_error(einval, normal),
|
||||||
|
ok = exit_on_activate_error(closed, normal),
|
||||||
|
ok = exit_on_activate_error(econnreset, {shutdown, econnreset}).
|
||||||
|
|
||||||
|
t_get_conn_info(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
#{sockinfo := SockInfo} = emqx_connection:info(CPid),
|
||||||
|
?assertEqual(#{active_n => 100,limiter => undefined,
|
||||||
|
peername => {{127,0,0,1},3456},
|
||||||
|
sockname => {{127,0,0,1},1883},
|
||||||
|
sockstate => running,
|
||||||
|
socktype => tcp}, SockInfo)
|
||||||
|
end).
|
||||||
|
|
||||||
|
g_get_conn_stats(_) ->
|
||||||
|
with_client(fun(CPid) ->
|
||||||
|
Stats = emqx_connection:stats(CPid),
|
||||||
|
ct:pal("==== stats: ~p", [Stats]),
|
||||||
|
[?assert(proplists:get_value(Key, Stats) >= 0) || Key <- ?STATS_KYES]
|
||||||
|
end, []).
|
||||||
|
|
||||||
|
t_handle_call_discard(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_call,
|
||||||
|
fun(discard, Channel) ->
|
||||||
|
{shutdown, discarded, ok, Channel}
|
||||||
|
end),
|
||||||
|
ok = emqx_connection:call(CPid, discard),
|
||||||
|
timer:sleep(100),
|
||||||
|
ok = trap_exit(CPid, {shutdown, discarded})
|
||||||
|
end, #{trap_exit => true}).
|
||||||
|
|
||||||
|
t_handle_call_takeover(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_call,
|
||||||
|
fun({takeover, 'begin'}, Channel) ->
|
||||||
|
{reply, session, Channel};
|
||||||
|
({takeover, 'end'}, Channel) ->
|
||||||
|
{shutdown, takeovered, [], Channel}
|
||||||
|
end),
|
||||||
|
session = emqx_connection:call(CPid, {takeover, 'begin'}),
|
||||||
|
[] = emqx_connection:call(CPid, {takeover, 'end'}),
|
||||||
|
timer:sleep(100),
|
||||||
|
ok = trap_exit(CPid, {shutdown, takeovered})
|
||||||
|
end, #{trap_exit => true}).
|
||||||
|
|
||||||
|
t_handle_call_any(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_call,
|
||||||
|
fun(_Req, Channel) -> {reply, ok, Channel} end),
|
||||||
|
ok = emqx_connection:call(CPid, req)
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_handle_incoming_connect(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_in, fun(_Packet, Channel) -> {ok, Channel} end),
|
||||||
|
ConnPkt = #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5,
|
||||||
|
proto_name = <<"MQTT">>,
|
||||||
|
clientid = <<>>,
|
||||||
|
clean_start = true,
|
||||||
|
keepalive = 60
|
||||||
|
},
|
||||||
|
Frame = make_frame(?CONNECT_PACKET(ConnPkt)),
|
||||||
|
CPid ! {tcp, sock, Frame}
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_handle_incoming_publish(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_in, fun(_Packet, Channel) -> {ok, Channel} end),
|
||||||
|
Frame = make_frame(?PUBLISH_PACKET(?QOS_1, <<"t">>, 1, <<"payload">>)),
|
||||||
|
CPid ! {tcp, sock, Frame}
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_handle_incoming_subscribe(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_in, fun(_Packet, Channel) -> {ok, Channel} end),
|
||||||
|
Frame = <<?SUBSCRIBE:4,2:4,11,0,2,0,6,84,111,112,105,99,65,2>>,
|
||||||
|
CPid ! {tcp, sock, Frame}
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_handle_incoming_unsubscribe(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_in, fun(_Packet, Channel) -> {ok, Channel} end),
|
||||||
|
Frame = <<?UNSUBSCRIBE:4,2:4,10,0,2,0,6,84,111,112,105,99,65>>,
|
||||||
|
CPid ! {tcp, sock, Frame}
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_handle_sock_error(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_info,
|
||||||
|
fun({_, Reason}, Channel) ->
|
||||||
|
{shutdown, Reason, Channel}
|
||||||
|
end),
|
||||||
|
%% TODO: fixme later
|
||||||
|
CPid ! {tcp_error, sock, econnreset},
|
||||||
|
timer:sleep(100),
|
||||||
|
trap_exit(CPid, {shutdown, econnreset})
|
||||||
|
end, #{trap_exit => true}).
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
t_handle_sock_closed(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_info,
|
||||||
|
fun({sock_closed, Reason}, Channel) ->
|
||||||
|
{shutdown, Reason, Channel}
|
||||||
|
end),
|
||||||
|
CPid ! {tcp_closed, sock},
|
||||||
|
timer:sleep(100),
|
||||||
|
%%TODO: closed?
|
||||||
|
trap_exit(CPid, {shutdown, closed})
|
||||||
|
end, #{trap_exit => true}).
|
||||||
|
|
||||||
|
t_handle_outgoing(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
Publish = ?PUBLISH_PACKET(?QOS_2, <<"Topic">>, 1, <<>>),
|
||||||
|
CPid ! {outgoing, Publish},
|
||||||
|
CPid ! {outgoing, ?PUBREL_PACKET(1)},
|
||||||
|
CPid ! {outgoing, [?PUBCOMP_PACKET(1)]}
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_conn_rate_limit(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_in, fun(_, Channel) -> {ok, Channel} end),
|
||||||
|
lists:foreach(fun(I) ->
|
||||||
|
Publish = ?PUBLISH_PACKET(?QOS_0, <<"Topic">>, I, payload(2000)),
|
||||||
|
CPid ! {tcp, sock, make_frame(Publish)}
|
||||||
|
end, [1, 2])
|
||||||
|
%%#{sockinfo := #{sockstate := blocked}} = emqx_connection:info(CPid)
|
||||||
|
end, #{active_n => 1, rate_limit => {1, 1024}}).
|
||||||
|
|
||||||
|
t_conn_pub_limit(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_in, fun(_, Channel) -> {ok, Channel} end),
|
||||||
|
ok = lists:foreach(fun(I) ->
|
||||||
|
CPid ! {incoming, ?PUBLISH_PACKET(?QOS_0, <<"Topic">>, I, <<>>)}
|
||||||
|
end, lists:seq(1, 3))
|
||||||
|
%%#{sockinfo := #{sockstate := blocked}} = emqx_connection:info(CPid)
|
||||||
|
end, #{active_n => 1, publish_limit => {1, 2}}).
|
||||||
|
|
||||||
|
t_oom_shutdown(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
CPid ! {shutdown, message_queue_too_long},
|
||||||
|
timer:sleep(100),
|
||||||
|
trap_exit(CPid, {shutdown, message_queue_too_long})
|
||||||
|
end, #{trap_exit => true}).
|
||||||
|
|
||||||
|
t_handle_idle_timeout(_) ->
|
||||||
|
ok = emqx_zone:set_env(external, idle_timeout, 10),
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
timer:sleep(100),
|
||||||
|
trap_exit(CPid, {shutdown, idle_timeout})
|
||||||
|
end, #{zone => external, trap_exit => true}).
|
||||||
|
|
||||||
|
t_handle_emit_stats(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_timeout,
|
||||||
|
fun(_TRef, _TMsg, Channel) ->
|
||||||
|
{ok, Channel}
|
||||||
|
end),
|
||||||
|
CPid ! {timeout, make_ref(), emit_stats}
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_handle_keepalive_timeout(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_timeout,
|
||||||
|
fun(_TRef, _TMsg, Channel) ->
|
||||||
|
{shutdown, keepalive_timeout, Channel}
|
||||||
|
end),
|
||||||
|
CPid ! {timeout, make_ref(), keepalive},
|
||||||
|
timer:sleep(100),
|
||||||
|
trap_exit(CPid, {shutdown, keepalive_timeout})
|
||||||
|
end, #{trap_exit => true}).
|
||||||
|
|
||||||
|
t_handle_shutdown(_) ->
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
CPid ! Shutdown = {shutdown, reason},
|
||||||
|
timer:sleep(100),
|
||||||
|
trap_exit(CPid, Shutdown)
|
||||||
|
end, #{trap_exit => true}).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Helper functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
exit_on_wait_error(SockErr, Reason) ->
|
||||||
|
ok = meck:expect(emqx_transport, wait,
|
||||||
|
fun(_Sock) ->
|
||||||
|
{error, SockErr}
|
||||||
|
end),
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
timer:sleep(100),
|
||||||
|
trap_exit(CPid, Reason)
|
||||||
|
end, #{trap_exit => true}).
|
||||||
|
|
||||||
|
exit_on_activate_error(SockErr, Reason) ->
|
||||||
|
ok = meck:expect(emqx_transport, setopts,
|
||||||
|
fun(_Sock, _Opts) ->
|
||||||
|
{error, SockErr}
|
||||||
|
end),
|
||||||
|
with_connection(fun(CPid) ->
|
||||||
|
timer:sleep(100),
|
||||||
|
trap_exit(CPid, Reason)
|
||||||
|
end, #{trap_exit => true}).
|
||||||
|
|
||||||
|
with_connection(TestFun) ->
|
||||||
|
with_connection(TestFun, #{trap_exit => false}).
|
||||||
|
|
||||||
|
with_connection(TestFun, Options) when is_map(Options) ->
|
||||||
|
with_connection(TestFun, maps:to_list(Options));
|
||||||
|
with_connection(TestFun, Options) ->
|
||||||
|
TrapExit = proplists:get_value(trap_exit, Options, false),
|
||||||
|
process_flag(trap_exit, TrapExit),
|
||||||
|
{ok, CPid} = emqx_connection:start_link(emqx_transport, sock, Options),
|
||||||
|
TestFun(CPid),
|
||||||
|
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),
|
{ok, _} = emqtt:connect(C),
|
||||||
{ok, _, [1]} = emqtt:subscribe(C, Topic, qos1),
|
timer:sleep(50),
|
||||||
{ok, _, [2]} = emqtt:subscribe(C, Topic, qos2),
|
case emqx_cm:lookup_channels(ClientId) of
|
||||||
{ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2),
|
[] -> ct:fail({client_not_started, ClientId});
|
||||||
{ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2),
|
[ChanPid] ->
|
||||||
{ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2),
|
TestFun(ChanPid),
|
||||||
?assertEqual(3, length(recv_msgs(3))),
|
emqtt:stop(C)
|
||||||
ok = emqtt:disconnect(C).
|
|
||||||
|
|
||||||
recv_msgs(Count) ->
|
|
||||||
recv_msgs(Count, []).
|
|
||||||
|
|
||||||
recv_msgs(0, Msgs) ->
|
|
||||||
Msgs;
|
|
||||||
recv_msgs(Count, Msgs) ->
|
|
||||||
receive
|
|
||||||
{publish, Msg} ->
|
|
||||||
recv_msgs(Count-1, [Msg|Msgs])
|
|
||||||
after 100 ->
|
|
||||||
Msgs
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
trap_exit(Pid, Reason) ->
|
||||||
|
receive
|
||||||
|
{'EXIT', Pid, Reason} -> ok;
|
||||||
|
{'EXIT', Pid, Other} -> error({unexpect_exit, Other})
|
||||||
|
after
|
||||||
|
0 -> error({expect_exit, Reason})
|
||||||
|
end.
|
||||||
|
|
||||||
|
make_frame(Packet) ->
|
||||||
|
iolist_to_binary(emqx_frame:serialize(Packet)).
|
||||||
|
|
||||||
|
payload(Len) -> iolist_to_binary(lists:duplicate(Len, 1)).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,4 +39,3 @@ t_guid_hexstr(_) ->
|
||||||
t_guid_base62(_) ->
|
t_guid_base62(_) ->
|
||||||
Guid = emqx_guid:gen(),
|
Guid = emqx_guid:gen(),
|
||||||
?assertEqual(Guid, emqx_guid:from_base62(emqx_guid:to_base62(Guid))).
|
?assertEqual(Guid, emqx_guid:from_base62(emqx_guid:to_base62(Guid))).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,21 @@
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
% t_lookup(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_run_fold(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_run(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_del(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_add(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
t_add_del_hook(_) ->
|
t_add_del_hook(_) ->
|
||||||
{ok, _} = emqx_hooks:start_link(),
|
{ok, _} = emqx_hooks:start_link(),
|
||||||
ok = emqx:hook(test_hook, fun ?MODULE:hook_fun1/1, []),
|
ok = emqx:hook(test_hook, fun ?MODULE:hook_fun1/1, []),
|
||||||
|
|
|
||||||
|
|
@ -90,3 +90,5 @@ t_window(_) ->
|
||||||
a, 1, emqx_inflight:new(2))),
|
a, 1, emqx_inflight:new(2))),
|
||||||
?assertEqual([a, b], emqx_inflight:window(Inflight)).
|
?assertEqual([a, b], emqx_inflight:window(Inflight)).
|
||||||
|
|
||||||
|
% t_to_list(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ end_per_suite(_Config) ->
|
||||||
|
|
||||||
t_start_stop_listeners(_) ->
|
t_start_stop_listeners(_) ->
|
||||||
ok = emqx_listeners:start(),
|
ok = emqx_listeners:start(),
|
||||||
|
{error, _} = emqx_listeners:start_listener({ws,{"127.0.0.1", 8083}, []}),
|
||||||
ok = emqx_listeners:stop().
|
ok = emqx_listeners:stop().
|
||||||
|
|
||||||
t_restart_listeners(_) ->
|
t_restart_listeners(_) ->
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_logger_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-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) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
t_debug(_) ->
|
||||||
|
?assertEqual(ok, ?LOGGER:debug("for_test")),
|
||||||
|
?assertEqual(ok, ?LOGGER:debug("for_test", [])),
|
||||||
|
?assertEqual(ok, ?LOGGER:debug(#{pid => self()}, "for_test", [])).
|
||||||
|
|
||||||
|
t_info(_) ->
|
||||||
|
?assertEqual(ok, ?LOGGER:info("for_test")),
|
||||||
|
?assertEqual(ok, ?LOGGER:info("for_test", [])),
|
||||||
|
?assertEqual(ok, ?LOGGER:info(#{pid => self()}, "for_test", [])).
|
||||||
|
|
||||||
|
t_warning(_) ->
|
||||||
|
?assertEqual(ok, ?LOGGER:warning("for_test")),
|
||||||
|
?assertEqual(ok, ?LOGGER:warning("for_test", [])),
|
||||||
|
?assertEqual(ok, ?LOGGER:warning(#{pid => self()}, "for_test", [])).
|
||||||
|
|
||||||
|
t_error(_) ->
|
||||||
|
?assertEqual(ok, ?LOGGER:error("for_test")),
|
||||||
|
?assertEqual(ok, ?LOGGER:error("for_test", [])),
|
||||||
|
?assertEqual(ok, ?LOGGER:error(#{pid => self()}, "for_test", [])).
|
||||||
|
|
||||||
|
t_critical(_) ->
|
||||||
|
?assertEqual(ok, ?LOGGER:critical("for_test")),
|
||||||
|
?assertEqual(ok, ?LOGGER:critical("for_test", [])),
|
||||||
|
?assertEqual(ok, ?LOGGER:critical(#{pid => self()}, "for_test", [])).
|
||||||
|
|
||||||
|
t_set_proc_metadata(_) ->
|
||||||
|
?assertEqual(ok, ?LOGGER:set_proc_metadata(#{pid => self()})).
|
||||||
|
|
||||||
|
t_primary_log_level(_) ->
|
||||||
|
?assertEqual(ok, ?LOGGER:set_primary_log_level(debug)),
|
||||||
|
?assertEqual(debug, ?LOGGER:get_primary_log_level()).
|
||||||
|
|
||||||
|
t_get_log_handlers(_) ->
|
||||||
|
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(_) ->
|
||||||
|
[{HandlerId, _, _} | _ ] = ?LOGGER:get_log_handlers(),
|
||||||
|
?assertMatch({HandlerId, _, _}, ?LOGGER:get_log_handler(HandlerId)).
|
||||||
|
|
||||||
|
t_set_log_handler_level(_) ->
|
||||||
|
[{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(_) ->
|
||||||
|
?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(_) ->
|
||||||
|
{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(_) ->
|
||||||
|
?assertEqual(ok, ?LOGGER:set_metadata_peername("for_test")).
|
||||||
|
|
||||||
|
t_set_metadata_clientid(_) ->
|
||||||
|
?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.
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
%%--------------------------------------------------------------------
|
%%
|
||||||
%% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved.
|
%% %CopyrightBegin%
|
||||||
|
%%
|
||||||
|
%% Copyright Ericsson AB 2018. All Rights Reserved.
|
||||||
%%
|
%%
|
||||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
%% you may not use this file except in compliance with the License.
|
%% you may not use this file except in compliance with the License.
|
||||||
|
|
@ -12,16 +14,21 @@
|
||||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
%%--------------------------------------------------------------------
|
%%
|
||||||
|
%% %CopyrightEnd%
|
||||||
|
%%
|
||||||
-module(emqx_logger_formatter_SUITE).
|
-module(emqx_logger_formatter_SUITE).
|
||||||
|
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
-compile(nowarn_export_all).
|
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
-include_lib("kernel/include/logger.hrl").
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
-define(TRY(X), my_try(fun() -> X end)).
|
||||||
|
|
||||||
|
suite() ->
|
||||||
|
[{timetrap,{seconds,30}}].
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
|
@ -29,45 +36,621 @@ init_per_suite(Config) ->
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_chars_limit(_Config) ->
|
init_per_group(_Group, Config) ->
|
||||||
CharsLimit = 50,
|
Config.
|
||||||
LogFilename = "./t_chars_limit.log",
|
|
||||||
Formatter = {emqx_logger_formatter,
|
|
||||||
#{chars_limit => CharsLimit}},
|
|
||||||
#{level := OrgLevel} = logger:get_primary_config(),
|
|
||||||
Config =
|
|
||||||
#{level => info,
|
|
||||||
config => #{
|
|
||||||
type => halt,
|
|
||||||
file => LogFilename},
|
|
||||||
formatter => Formatter},
|
|
||||||
logger:add_handler(t_handler, logger_disk_log_h, Config),
|
|
||||||
logger:set_primary_config(level, info),
|
|
||||||
|
|
||||||
logger:info("hello"),
|
end_per_group(_Group, _Config) ->
|
||||||
logger:info(lists:duplicate(10, "hello")),
|
ok.
|
||||||
logger_disk_log_h:filesync(t_handler),
|
|
||||||
|
|
||||||
ct:pal("content : ~p", [file:read_file(LogFilename)]),
|
init_per_testcase(_TestCase, Config) ->
|
||||||
[FirstLine, SecondLine] = readlines(LogFilename),
|
Config.
|
||||||
|
|
||||||
?assertMatch([_Date, _Time, _Level, "hello\n"], string:split(FirstLine, " ", all)),
|
end_per_testcase(Case, Config) ->
|
||||||
?assert(length(SecondLine) =< 50),
|
try apply(?MODULE,Case,[cleanup,Config])
|
||||||
|
catch error:undef -> ok
|
||||||
|
end,
|
||||||
|
ok.
|
||||||
|
|
||||||
logger:set_primary_config(level, OrgLevel).
|
groups() ->
|
||||||
|
[].
|
||||||
|
|
||||||
|
all() ->
|
||||||
|
[default,
|
||||||
|
single_line,
|
||||||
|
template,
|
||||||
|
format_msg,
|
||||||
|
report_cb,
|
||||||
|
max_size,
|
||||||
|
depth,
|
||||||
|
chars_limit,
|
||||||
|
format_mfa,
|
||||||
|
level_or_msg_in_meta,
|
||||||
|
faulty_log,
|
||||||
|
faulty_config,
|
||||||
|
faulty_msg,
|
||||||
|
check_config,
|
||||||
|
update_config].
|
||||||
|
|
||||||
|
default(_Config) ->
|
||||||
|
String1 = format(info,{"~p",[term]},#{},#{}),
|
||||||
|
ct:log(String1),
|
||||||
|
?assertMatch([_Date, _Time,"info:","term\n"], string:lexemes(String1," ")),
|
||||||
|
|
||||||
|
Time = timestamp(),
|
||||||
|
ExpectedTimestamp = default_time_format(Time),
|
||||||
|
String2 = format(info,{"~p",[term]},#{time=>Time},#{}),
|
||||||
|
ct:log("ExpectedTimestamp: ~p, got: ~p", [ExpectedTimestamp, String2]),
|
||||||
|
" info: term\n" = string:prefix(String2,ExpectedTimestamp),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
single_line(_Config) ->
|
||||||
|
Time = timestamp(),
|
||||||
|
ExpectedTimestamp = default_time_format(Time),
|
||||||
|
String1 = format(info,{"~p",[term]},#{time=>Time},#{}),
|
||||||
|
ct:log(String1),
|
||||||
|
?assertMatch(" info: term\n", string:prefix(String1,ExpectedTimestamp)),
|
||||||
|
|
||||||
|
String2 = format(info,{"a:~n~p",[term]},#{time=>Time},#{}),
|
||||||
|
ct:log(String2),
|
||||||
|
?assertMatch(" info: a:\nterm\n", string:prefix(String2,ExpectedTimestamp)),
|
||||||
|
|
||||||
|
|
||||||
readlines(FileName) ->
|
Prefix =
|
||||||
{ok, Device} = file:open(FileName, [read]),
|
"Some characters to fill the line ------------------------------------- ",
|
||||||
try get_all_lines(Device)
|
%% There would actually be newlines inside the
|
||||||
after file:close(Device)
|
%% list and map.
|
||||||
end.
|
String4 = format(info,{"~s~p~n~s~p~n",[Prefix,
|
||||||
|
lists:seq(1,10),
|
||||||
|
Prefix,
|
||||||
|
#{a=>map,with=>a,few=>accociations}]},
|
||||||
|
#{time=>Time},
|
||||||
|
#{}),
|
||||||
|
ct:log(String4),
|
||||||
|
match = re:run(String4,"\\[1,2,3,\n",[global,{capture,none}]),
|
||||||
|
{match,Match4} = re:run(String4,"=>\n",[global,{capture,all}]),
|
||||||
|
3 = length(Match4),
|
||||||
|
|
||||||
get_all_lines(Device) ->
|
%% Test that big metadata fields do not get line breaks
|
||||||
get_all_lines(Device, []).
|
String5 = format(info,"",
|
||||||
get_all_lines(Device, All) ->
|
#{mymeta=>lists:seq(1,100)},
|
||||||
case io:get_line(Device, "") of
|
#{template=>[mymeta,"\n"]}),
|
||||||
eof ->
|
ct:log(String5),
|
||||||
lists:reverse(All);
|
[_] = string:lexemes(String5,"\n"),
|
||||||
Line -> get_all_lines(Device, [Line | All])
|
ok.
|
||||||
end.
|
|
||||||
|
template(_Config) ->
|
||||||
|
Time = timestamp(),
|
||||||
|
|
||||||
|
Template1 = [msg],
|
||||||
|
String1 = format(info,{"~p",[term]},#{time=>Time},#{template=>Template1}),
|
||||||
|
ct:log(String1),
|
||||||
|
"term" = String1,
|
||||||
|
|
||||||
|
Template2 = [msg,unknown],
|
||||||
|
String2 = format(info,{"~p",[term]},#{time=>Time},#{template=>Template2}),
|
||||||
|
ct:log(String2),
|
||||||
|
"term" = String2,
|
||||||
|
|
||||||
|
Template3 = ["string"],
|
||||||
|
String3 = format(info,{"~p",[term]},#{time=>Time},#{template=>Template3}),
|
||||||
|
ct:log(String3),
|
||||||
|
"string" = String3,
|
||||||
|
|
||||||
|
Template4 = ["string\nnewline"],
|
||||||
|
String4 = format(info,{"~p",[term]},#{time=>Time},#{template=>Template4}),
|
||||||
|
ct:log(String4),
|
||||||
|
"string\nnewline" = String4,
|
||||||
|
|
||||||
|
Template5 = [],
|
||||||
|
String5 = format(info,{"~p",[term]},#{time=>Time},#{template=>Template5}),
|
||||||
|
ct:log(String5),
|
||||||
|
"" = String5,
|
||||||
|
|
||||||
|
Ref6 = erlang:make_ref(),
|
||||||
|
Meta6 = #{atom=>some_atom,
|
||||||
|
integer=>632,
|
||||||
|
list=>[list,"string",4321,#{},{tuple}],
|
||||||
|
mfa=>{mod,func,0},
|
||||||
|
pid=>self(),
|
||||||
|
ref=>Ref6,
|
||||||
|
string=>"some string",
|
||||||
|
time=>Time,
|
||||||
|
tuple=>{1,atom,"list"},
|
||||||
|
nested=>#{subkey=>subvalue}},
|
||||||
|
Template6 = lists:join(";",lists:sort(maps:keys(maps:remove(nested,Meta6))) ++
|
||||||
|
[[nested,subkey]]),
|
||||||
|
String6 = format(info,{"~p",[term]},Meta6,#{template=>Template6}),
|
||||||
|
ct:log(String6),
|
||||||
|
SelfStr = pid_to_list(self()),
|
||||||
|
RefStr6 = ref_to_list(Ref6),
|
||||||
|
ListStr = "[list,\"string\",4321,#{},{tuple}]",
|
||||||
|
ExpectedTime6 = default_time_format(Time),
|
||||||
|
["some_atom",
|
||||||
|
"632",
|
||||||
|
ListStr,
|
||||||
|
"mod:func/0",
|
||||||
|
SelfStr,
|
||||||
|
RefStr6,
|
||||||
|
"some string",
|
||||||
|
ExpectedTime6,
|
||||||
|
"{1,atom,\"list\"}",
|
||||||
|
"subvalue"] = string:lexemes(String6,";"),
|
||||||
|
|
||||||
|
Meta7 = #{time=>Time,
|
||||||
|
nested=>#{key1=>#{subkey1=>value1},
|
||||||
|
key2=>value2}},
|
||||||
|
Template7 = lists:join(";",[nested,
|
||||||
|
[nested,key1],
|
||||||
|
[nested,key1,subkey1],
|
||||||
|
[nested,key2],
|
||||||
|
[nested,key2,subkey2],
|
||||||
|
[nested,key3],
|
||||||
|
[nested,key3,subkey3]]),
|
||||||
|
String7 = format(info,{"~p",[term]},Meta7,#{template=>Template7}),
|
||||||
|
ct:log(String7),
|
||||||
|
[MultipleKeysStr7,
|
||||||
|
"#{subkey1 => value1}",
|
||||||
|
"value1",
|
||||||
|
"value2",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""] = string:split(String7,";",all),
|
||||||
|
%% Order of keys is not fixed
|
||||||
|
case MultipleKeysStr7 of
|
||||||
|
"#{key2 => value2,key1 => #{subkey1 => value1}}" -> ok;
|
||||||
|
"#{key1 => #{subkey1 => value1},key2 => value2}" -> ok;
|
||||||
|
_ -> ct:fail({full_nested_map_unexpected,MultipleKeysStr7})
|
||||||
|
end,
|
||||||
|
|
||||||
|
Meta8 = #{time=>Time,
|
||||||
|
nested=>#{key1=>#{subkey1=>value1},
|
||||||
|
key2=>value2}},
|
||||||
|
Template8 =
|
||||||
|
lists:join(
|
||||||
|
";",
|
||||||
|
[{nested,["exist:",nested],["noexist"]},
|
||||||
|
{[nested,key1],["exist:",[nested,key1]],["noexist"]},
|
||||||
|
{[nested,key1,subkey1],["exist:",[nested,key1,subkey1]],["noexist"]},
|
||||||
|
{[nested,key2],["exist:",[nested,key2]],["noexist"]},
|
||||||
|
{[nested,key2,subkey2],["exist:",[nested,key2,subkey2]],["noexist"]},
|
||||||
|
{[nested,key3],["exist:",[nested,key3]],["noexist"]},
|
||||||
|
{[nested,key3,subkey3],["exist:",[nested,key3,subkey3]],["noexist"]}]),
|
||||||
|
String8 = format(info,{"~p",[term]},Meta8,#{template=>Template8}),
|
||||||
|
ct:log(String8),
|
||||||
|
[MultipleKeysStr8,
|
||||||
|
"exist:#{subkey1 => value1}",
|
||||||
|
"exist:value1",
|
||||||
|
"exist:value2",
|
||||||
|
"noexist",
|
||||||
|
"noexist",
|
||||||
|
"noexist"] = string:split(String8,";",all),
|
||||||
|
%% Order of keys is not fixed
|
||||||
|
case MultipleKeysStr8 of
|
||||||
|
"exist:#{key2 => value2,key1 => #{subkey1 => value1}}" -> ok;
|
||||||
|
"exist:#{key1 => #{subkey1 => value1},key2 => value2}" -> ok;
|
||||||
|
_ -> ct:fail({full_nested_map_unexpected,MultipleKeysStr8})
|
||||||
|
end,
|
||||||
|
|
||||||
|
ok.
|
||||||
|
|
||||||
|
format_msg(_Config) ->
|
||||||
|
Template = [msg],
|
||||||
|
|
||||||
|
String1 = format(info,{"~p",[term]},#{},#{template=>Template}),
|
||||||
|
ct:log(String1),
|
||||||
|
"term" = String1,
|
||||||
|
|
||||||
|
String2 = format(info,{"list",[term]},#{},#{template=>Template}),
|
||||||
|
ct:log(String2),
|
||||||
|
"FORMAT ERROR: \"list\" - [term]" = String2,
|
||||||
|
|
||||||
|
String3 = format(info,{report,term},#{},#{template=>Template}),
|
||||||
|
ct:log(String3),
|
||||||
|
"term" = String3,
|
||||||
|
|
||||||
|
String4 = format(info,{report,term},
|
||||||
|
#{report_cb=>fun(_)-> {"formatted",[]} end},
|
||||||
|
#{template=>Template}),
|
||||||
|
ct:log(String4),
|
||||||
|
"formatted" = String4,
|
||||||
|
|
||||||
|
String5 = format(info,{report,term},
|
||||||
|
#{report_cb=>fun(_)-> faulty_return end},
|
||||||
|
#{template=>Template}),
|
||||||
|
ct:log(String5),
|
||||||
|
"REPORT_CB/1 ERROR: term; Returned: faulty_return" = String5,
|
||||||
|
|
||||||
|
String6 = format(info,{report,term},
|
||||||
|
#{report_cb=>fun(_)-> erlang:error(fun_crashed) end},
|
||||||
|
#{template=>Template}),
|
||||||
|
ct:log(String6),
|
||||||
|
"REPORT_CB/1 CRASH: term; Reason: {error,fun_crashed,"++_ = String6,
|
||||||
|
|
||||||
|
String7 = format(info,{report,term},
|
||||||
|
#{report_cb=>fun(_,_)-> ['not',a,string] end},
|
||||||
|
#{template=>Template}),
|
||||||
|
ct:log(String7),
|
||||||
|
"REPORT_CB/2 ERROR: term; Returned: ['not',a,string]" = String7,
|
||||||
|
|
||||||
|
String8 = format(info,{report,term},
|
||||||
|
#{report_cb=>fun(_,_)-> faulty_return end},
|
||||||
|
#{template=>Template}),
|
||||||
|
ct:log(String8),
|
||||||
|
"REPORT_CB/2 ERROR: term; Returned: faulty_return" = String8,
|
||||||
|
|
||||||
|
String9 = format(info,{report,term},
|
||||||
|
#{report_cb=>fun(_,_)-> erlang:error(fun_crashed) end},
|
||||||
|
#{template=>Template}),
|
||||||
|
ct:log(String9),
|
||||||
|
"REPORT_CB/2 CRASH: term; Reason: {error,fun_crashed,"++_ = String9,
|
||||||
|
|
||||||
|
%% strings are not formatted
|
||||||
|
String10 = format(info,{string,"string"},
|
||||||
|
#{report_cb=>fun(_)-> {"formatted",[]} end},
|
||||||
|
#{template=>Template}),
|
||||||
|
ct:log(String10),
|
||||||
|
"string" = String10,
|
||||||
|
|
||||||
|
String11 = format(info,{string,['not',printable,list]},
|
||||||
|
#{report_cb=>fun(_)-> {"formatted",[]} end},
|
||||||
|
#{template=>Template}),
|
||||||
|
ct:log("~ts",[String11]), % avoiding ct_log crash
|
||||||
|
"FORMAT ERROR: \"~ts\" - [['not',printable,list]]" = String11,
|
||||||
|
|
||||||
|
String12 = format(info,{string,"string"},#{},#{template=>Template}),
|
||||||
|
ct:log(String12),
|
||||||
|
"string" = String12,
|
||||||
|
|
||||||
|
ok.
|
||||||
|
|
||||||
|
report_cb(_Config) ->
|
||||||
|
Template = [msg],
|
||||||
|
MetaFun = fun(_) -> {"meta_rcb",[]} end,
|
||||||
|
ConfigFun = fun(_) -> {"config_rcb",[]} end,
|
||||||
|
"term" = format(info,{report,term},#{},#{template=>Template}),
|
||||||
|
"meta_rcb" =
|
||||||
|
format(info,{report,term},#{report_cb=>MetaFun},#{template=>Template}),
|
||||||
|
"config_rcb" =
|
||||||
|
format(info,{report,term},#{},#{template=>Template,
|
||||||
|
report_cb=>ConfigFun}),
|
||||||
|
"config_rcb" =
|
||||||
|
format(info,{report,term},#{report_cb=>MetaFun},#{template=>Template,
|
||||||
|
report_cb=>ConfigFun}),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
max_size(_Config) ->
|
||||||
|
Cfg = #{template=>[msg]},
|
||||||
|
"12345678901234567890" = format(info,{"12345678901234567890",[]},#{},Cfg),
|
||||||
|
%% application:set_env(kernel,logger_max_size,11),
|
||||||
|
%% "12345678901234567890" = % min value is 50, so this is not limited
|
||||||
|
%% format(info,{"12345678901234567890",[]},#{},Cfg),
|
||||||
|
%% "12345678901234567890123456789012345678901234567..." = % 50
|
||||||
|
%% format(info,
|
||||||
|
%% {"123456789012345678901234567890123456789012345678901234567890",
|
||||||
|
%% []},
|
||||||
|
%% #{},
|
||||||
|
%% Cfg),
|
||||||
|
%% application:set_env(kernel,logger_max_size,53),
|
||||||
|
%% "12345678901234567890123456789012345678901234567890..." = %53
|
||||||
|
%% format(info,
|
||||||
|
%% {"123456789012345678901234567890123456789012345678901234567890",
|
||||||
|
%% []},
|
||||||
|
%% #{},
|
||||||
|
%% Cfg),
|
||||||
|
"123456789012..." =
|
||||||
|
format(info,{"12345678901234567890",[]},#{},Cfg#{max_size=>15}),
|
||||||
|
"12345678901234567890" =
|
||||||
|
format(info,{"12345678901234567890",[]},#{},Cfg#{max_size=>unlimited}),
|
||||||
|
%% Check that one newline at the end of the line is kept (if it exists)
|
||||||
|
"12345678901...\n" =
|
||||||
|
format(info,{"12345678901234567890\n",[]},#{},Cfg#{max_size=>15}),
|
||||||
|
"12345678901...\n" =
|
||||||
|
format(info,{"12345678901234567890",[]},#{},Cfg#{template=>[msg,"\n"],
|
||||||
|
max_size=>15}),
|
||||||
|
ok.
|
||||||
|
max_size(cleanup,_Config) ->
|
||||||
|
application:unset_env(kernel,logger_max_size),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
depth(_Config) ->
|
||||||
|
Template = [msg],
|
||||||
|
"[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]" =
|
||||||
|
format(info,
|
||||||
|
{"~p",[[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]]},
|
||||||
|
#{},
|
||||||
|
#{template=>Template}),
|
||||||
|
application:set_env(kernel,error_logger_format_depth,11),
|
||||||
|
"[1,2,3,4,5,6,7,8,9,0|...]" =
|
||||||
|
format(info,
|
||||||
|
{"~p",[[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]]},
|
||||||
|
#{},
|
||||||
|
#{template=>Template}),
|
||||||
|
"[1,2,3,4,5,6,7,8,9,0,1,2|...]" =
|
||||||
|
format(info,
|
||||||
|
{"~p",[[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]]},
|
||||||
|
#{},
|
||||||
|
#{template=>Template,
|
||||||
|
depth=>13}),
|
||||||
|
"[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]" =
|
||||||
|
format(info,
|
||||||
|
{"~p",[[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]]},
|
||||||
|
#{},
|
||||||
|
#{template=>Template,
|
||||||
|
depth=>unlimited}),
|
||||||
|
ok.
|
||||||
|
depth(cleanup,_Config) ->
|
||||||
|
application:unset_env(kernel,error_logger_format_depth),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
chars_limit(_Config) ->
|
||||||
|
FA = {"LoL: ~p~nL: ~p~nMap: ~p~n",
|
||||||
|
[lists:duplicate(10,lists:seq(1,100)),
|
||||||
|
lists:seq(1,100),
|
||||||
|
maps:from_list(lists:zip(lists:seq(1,100),
|
||||||
|
lists:duplicate(100,value)))]},
|
||||||
|
Meta = #{time=>timestamp()},
|
||||||
|
Template = [time," - ", msg, "\n"],
|
||||||
|
FC = #{template=>Template,
|
||||||
|
depth=>unlimited,
|
||||||
|
max_size=>unlimited,
|
||||||
|
chars_limit=>unlimited},
|
||||||
|
CL1 = 80,
|
||||||
|
String1 = format(info,FA,Meta,FC#{chars_limit=>CL1}),
|
||||||
|
L1 = string:length(String1),
|
||||||
|
ct:log("String1: ~p~nLength1: ~p~n",[lists:flatten(String1),L1]),
|
||||||
|
true = L1 > CL1,
|
||||||
|
true = L1 < CL1 + 15,
|
||||||
|
|
||||||
|
String2 = format(info,FA,Meta,FC#{chars_limit=>CL1,depth=>10}),
|
||||||
|
L2 = string:length(String2),
|
||||||
|
ct:log("String2: ~p~nLength2: ~p~n",[lists:flatten(String2),L2]),
|
||||||
|
String2 = String1,
|
||||||
|
|
||||||
|
CL3 = 200,
|
||||||
|
String3 = format(info,FA,Meta,FC#{chars_limit=>CL3}),
|
||||||
|
L3 = string:length(String3),
|
||||||
|
ct:log("String3: ~p~nLength3: ~p~n",[lists:flatten(String3),L3]),
|
||||||
|
true = L3 > CL3,
|
||||||
|
true = L3 < CL3 + 15,
|
||||||
|
|
||||||
|
String4 = format(info,FA,Meta,FC#{chars_limit=>CL3,depth=>10}),
|
||||||
|
L4 = string:length(String4),
|
||||||
|
ct:log("String4: ~p~nLength4: ~p~n",[lists:flatten(String4),L4]),
|
||||||
|
true = L4 > CL3,
|
||||||
|
true = L4 < CL3 + 15,
|
||||||
|
|
||||||
|
%% Test that max_size truncates the string which is limited by
|
||||||
|
%% depth and chars_limit
|
||||||
|
MS5 = 150,
|
||||||
|
String5 = format(info,FA,Meta,FC#{chars_limit=>CL3,depth=>10,max_size=>MS5}),
|
||||||
|
L5 = string:length(String5),
|
||||||
|
ct:log("String5: ~p~nLength5: ~p~n",[String5,L5]),
|
||||||
|
L5 = MS5,
|
||||||
|
true = lists:prefix(lists:sublist(String5,L5-4),String4),
|
||||||
|
|
||||||
|
%% Test that chars_limit limits string also
|
||||||
|
Str = "123456789012345678901234567890123456789012345678901234567890123456789",
|
||||||
|
CL6 = 80,
|
||||||
|
String6 = format(info,{string,Str},Meta,FC#{chars_limit=>CL6}),
|
||||||
|
L6 = string:length(String6),
|
||||||
|
ct:log("String6: ~p~nLength6: ~p~n",[String6,L6]),
|
||||||
|
L6 = CL6,
|
||||||
|
|
||||||
|
ok.
|
||||||
|
|
||||||
|
format_mfa(_Config) ->
|
||||||
|
Template = [mfa],
|
||||||
|
|
||||||
|
Meta1 = #{mfa=>{mod,func,0}},
|
||||||
|
String1 = format(info,{"~p",[term]},Meta1,#{template=>Template}),
|
||||||
|
ct:log(String1),
|
||||||
|
"mod:func/0" = String1,
|
||||||
|
|
||||||
|
Meta2 = #{mfa=>{mod,func,[]}},
|
||||||
|
String2 = format(info,{"~p",[term]},Meta2,#{template=>Template}),
|
||||||
|
ct:log(String2),
|
||||||
|
"mod:func/0" = String2,
|
||||||
|
|
||||||
|
Meta3 = #{mfa=>"mod:func/0"},
|
||||||
|
String3 = format(info,{"~p",[term]},Meta3,#{template=>Template}),
|
||||||
|
ct:log(String3),
|
||||||
|
"mod:func/0" = String3,
|
||||||
|
|
||||||
|
Meta4 = #{mfa=>othermfa},
|
||||||
|
String4 = format(info,{"~p",[term]},Meta4,#{template=>Template}),
|
||||||
|
ct:log(String4),
|
||||||
|
"othermfa" = String4,
|
||||||
|
|
||||||
|
ok.
|
||||||
|
|
||||||
|
level_or_msg_in_meta(_Config) ->
|
||||||
|
%% The template contains atoms to pick out values from meta,
|
||||||
|
%% or level/msg to add these from the log event. What if you have
|
||||||
|
%% a key named 'level' or 'msg' in meta and want to display
|
||||||
|
%% its value?
|
||||||
|
%% For now we simply ignore Meta on this and display the
|
||||||
|
%% actual level and msg from the log event.
|
||||||
|
|
||||||
|
Meta = #{level=>mylevel,
|
||||||
|
msg=>"metamsg"},
|
||||||
|
Template = [level,";",msg],
|
||||||
|
String = format(info,{"~p",[term]},Meta,#{template=>Template}),
|
||||||
|
ct:log(String),
|
||||||
|
"info;term" = String, % so mylevel and "metamsg" are ignored
|
||||||
|
ok.
|
||||||
|
|
||||||
|
faulty_log(_Config) ->
|
||||||
|
%% Unexpected log (should be type logger:log_event()) - print error
|
||||||
|
{error,
|
||||||
|
function_clause,
|
||||||
|
{emqx_logger_formatter,format,[_,_],_}} =
|
||||||
|
?TRY(emqx_logger_formatter:format(unexp_log,#{})),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
faulty_config(_Config) ->
|
||||||
|
{error,
|
||||||
|
function_clause,
|
||||||
|
{emqx_logger_formatter,format,[_,_],_}} =
|
||||||
|
?TRY(emqx_logger_formatter:format(#{level=>info,
|
||||||
|
msg=>{"~p",[term]},
|
||||||
|
meta=>#{time=>timestamp()}},
|
||||||
|
unexp_config)),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
faulty_msg(_Config) ->
|
||||||
|
{error,
|
||||||
|
function_clause,
|
||||||
|
{emqx_logger_formatter,_,_,_}} =
|
||||||
|
?TRY(emqx_logger_formatter:format(#{level=>info,
|
||||||
|
msg=>term,
|
||||||
|
meta=>#{time=>timestamp()}},
|
||||||
|
#{})),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
-define(cfgerr(X), {error,{invalid_formatter_config,emqx_logger_formatter,X}}).
|
||||||
|
check_config(_Config) ->
|
||||||
|
ok = emqx_logger_formatter:check_config(#{}),
|
||||||
|
?cfgerr(bad) = emqx_logger_formatter:check_config(bad),
|
||||||
|
|
||||||
|
C1 = #{chars_limit => 1,
|
||||||
|
depth => 1,
|
||||||
|
max_size => 1,
|
||||||
|
report_cb => fun(R) -> {"~p",[R]} end,
|
||||||
|
template => []},
|
||||||
|
ok = emqx_logger_formatter:check_config(C1),
|
||||||
|
|
||||||
|
ok = emqx_logger_formatter:check_config(#{chars_limit => unlimited}),
|
||||||
|
?cfgerr({chars_limit,bad}) =
|
||||||
|
emqx_logger_formatter:check_config(#{chars_limit => bad}),
|
||||||
|
|
||||||
|
ok = emqx_logger_formatter:check_config(#{depth => unlimited}),
|
||||||
|
?cfgerr({depth,bad}) =
|
||||||
|
emqx_logger_formatter:check_config(#{depth => bad}),
|
||||||
|
|
||||||
|
ok = emqx_logger_formatter:check_config(#{max_size => unlimited}),
|
||||||
|
?cfgerr({max_size,bad}) =
|
||||||
|
emqx_logger_formatter:check_config(#{max_size => bad}),
|
||||||
|
|
||||||
|
ok =
|
||||||
|
emqx_logger_formatter:check_config(#{report_cb => fun(_,_) -> "" end}),
|
||||||
|
?cfgerr({report_cb,F}) =
|
||||||
|
emqx_logger_formatter:check_config(#{report_cb => F=fun(_,_,_) -> {"",[]} end}),
|
||||||
|
?cfgerr({report_cb,bad}) =
|
||||||
|
emqx_logger_formatter:check_config(#{report_cb => bad}),
|
||||||
|
|
||||||
|
Ts = [[key],
|
||||||
|
[[key1,key2]],
|
||||||
|
[{key,[key],[]}],
|
||||||
|
[{[key1,key2],[[key1,key2]],["noexist"]}],
|
||||||
|
["string"]],
|
||||||
|
[begin
|
||||||
|
ct:log("check template: ~p",[T]),
|
||||||
|
ok = emqx_logger_formatter:check_config(#{template => T})
|
||||||
|
end
|
||||||
|
|| T <- Ts],
|
||||||
|
|
||||||
|
ETs = [bad,
|
||||||
|
[{key,bad}],
|
||||||
|
[{key,[key],bad}],
|
||||||
|
[{key,[key],"bad"}],
|
||||||
|
"bad",
|
||||||
|
[[key,$a,$b,$c]],
|
||||||
|
[[$a,$b,$c,key]]],
|
||||||
|
[begin
|
||||||
|
ct:log("check template: ~p",[T]),
|
||||||
|
{error,{invalid_formatter_template,emqx_logger_formatter,T}} =
|
||||||
|
emqx_logger_formatter:check_config(#{template => T})
|
||||||
|
end
|
||||||
|
|| T <- ETs],
|
||||||
|
ok.
|
||||||
|
|
||||||
|
%% Test that formatter config can be changed, and that the default
|
||||||
|
%% template is updated accordingly
|
||||||
|
update_config(_Config) ->
|
||||||
|
#{level := OldLevel} = logger:get_primary_config(),
|
||||||
|
logger:set_primary_config(level, debug),
|
||||||
|
{error,{not_found,?MODULE}} = logger:update_formatter_config(?MODULE,#{}),
|
||||||
|
|
||||||
|
logger:add_handler_filter(default,silence,{fun(_,_) -> stop end,ok}),
|
||||||
|
ok = logger:add_handler(?MODULE,?MODULE,#{formatter => {emqx_logger_formatter, #{chars_limit => unlimited}},
|
||||||
|
config => #{type => standard_io}}),
|
||||||
|
D = lists:seq(1,1000),
|
||||||
|
logger:notice("~p~n",[D]),
|
||||||
|
{Lines1,C1} = check_log(),
|
||||||
|
ct:log("lines1: ~p", [Lines1]),
|
||||||
|
ct:log("c1: ~p",[C1]),
|
||||||
|
[Line1 | _] = Lines1,
|
||||||
|
[_Date, WithOutDate1] = string:split(Line1," "),
|
||||||
|
[_, "notice: "++D1] = string:split(WithOutDate1," "),
|
||||||
|
?assert(length(D1)<1000),
|
||||||
|
?assertMatch(#{chars_limit := unlimited}, C1),
|
||||||
|
|
||||||
|
error_logger:error_msg("~p",[D]),
|
||||||
|
{Lines5,C5} = check_log(),
|
||||||
|
ct:log("Lines5: ~p", [Lines5]),
|
||||||
|
ct:log("c5: ~p",[C5]),
|
||||||
|
[Line5 | _] = Lines5,
|
||||||
|
[_Date, WithOutDate5] = string:split(Line5," "),
|
||||||
|
[_, "error: "++_D5] = string:split(WithOutDate5," "),
|
||||||
|
|
||||||
|
?assertMatch({error,{invalid_formatter_config,bad}},
|
||||||
|
logger:update_formatter_config(?MODULE,bad)),
|
||||||
|
?assertMatch({error,{invalid_formatter_config,emqx_logger_formatter,{depth,bad}}},
|
||||||
|
logger:update_formatter_config(?MODULE,depth,bad)),
|
||||||
|
|
||||||
|
logger:set_primary_config(level, OldLevel),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
update_config(cleanup,_Config) ->
|
||||||
|
_ = logger:remove_handler(?MODULE),
|
||||||
|
_ = logger:remove_handler_filter(default,silence),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
%%%-----------------------------------------------------------------
|
||||||
|
%%% Internal
|
||||||
|
format(Level,Msg,Meta,Config) ->
|
||||||
|
format(#{level=>Level,msg=>Msg,meta=>add_time(Meta)},Config).
|
||||||
|
|
||||||
|
format(Log,Config) ->
|
||||||
|
lists:flatten(emqx_logger_formatter:format(Log,Config)).
|
||||||
|
|
||||||
|
default_time_format(SysTime) when is_integer(SysTime) ->
|
||||||
|
Ms = SysTime rem 1000000 div 1000,
|
||||||
|
{Date, _Time = {H, Mi, S}} = calendar:system_time_to_local_time(SysTime, microsecond),
|
||||||
|
default_time_format({Date, {H, Mi, S, Ms}});
|
||||||
|
default_time_format({{Y, M, D}, {H, Mi, S, Ms}}) ->
|
||||||
|
io_lib:format("~b-~2..0b-~2..0b ~2..0b:~2..0b:~2..0b.~3..0b", [Y, M, D, H, Mi, S, Ms]);
|
||||||
|
default_time_format({{Y, M, D}, {H, Mi, S}}) ->
|
||||||
|
io_lib:format("~b-~2..0b-~2..0b ~2..0b:~2..0b:~2..0b", [Y, M, D, H, Mi, S]).
|
||||||
|
|
||||||
|
integer(Str) ->
|
||||||
|
is_integer(list_to_integer(Str)).
|
||||||
|
integer(Str,Max) ->
|
||||||
|
integer(Str,0,Max).
|
||||||
|
integer(Str,Min,Max) ->
|
||||||
|
Int = list_to_integer(Str),
|
||||||
|
Int >= Min andalso Int =<Max.
|
||||||
|
|
||||||
|
%%%-----------------------------------------------------------------
|
||||||
|
%%% Called by macro ?TRY(X)
|
||||||
|
my_try(Fun) ->
|
||||||
|
try Fun() catch C:R:S -> {C,R,hd(S)} end.
|
||||||
|
|
||||||
|
timestamp() ->
|
||||||
|
erlang:system_time(microsecond).
|
||||||
|
|
||||||
|
%% necessary?
|
||||||
|
add_time(#{time:=_}=Meta) ->
|
||||||
|
Meta;
|
||||||
|
add_time(Meta) ->
|
||||||
|
Meta#{time=>timestamp()}.
|
||||||
|
|
||||||
|
%%%-----------------------------------------------------------------
|
||||||
|
%%% handler callback
|
||||||
|
log(Log,#{formatter:={M,C}}) ->
|
||||||
|
put(log,{M:format(Log,C),C}),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
check_log() ->
|
||||||
|
{S,C} = erase(log),
|
||||||
|
{string:lexemes(S,"\n"),C}.
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ t_format(_) ->
|
||||||
},
|
},
|
||||||
io:format("~s~n", [emqx_message:format(Msg1)]).
|
io:format("~s~n", [emqx_message:format(Msg1)]).
|
||||||
|
|
||||||
t_expired(_) ->
|
t_is_expired(_) ->
|
||||||
Msg = emqx_message:make(<<"clientid">>, <<"topic">>, <<"payload">>),
|
Msg = emqx_message:make(<<"clientid">>, <<"topic">>, <<"payload">>),
|
||||||
?assertNot(emqx_message:is_expired(Msg)),
|
?assertNot(emqx_message:is_expired(Msg)),
|
||||||
Msg1 = emqx_message:set_headers(#{'Message-Expiry-Interval' => 1}, Msg),
|
Msg1 = emqx_message:set_headers(#{'Message-Expiry-Interval' => 1}, Msg),
|
||||||
|
|
@ -115,6 +115,9 @@ t_expired(_) ->
|
||||||
Msg2 = emqx_message:update_expiry(Msg1),
|
Msg2 = emqx_message:update_expiry(Msg1),
|
||||||
?assertEqual(1, emqx_message:get_header('Message-Expiry-Interval', Msg2)).
|
?assertEqual(1, emqx_message:get_header('Message-Expiry-Interval', Msg2)).
|
||||||
|
|
||||||
|
% t_to_list(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
t_to_packet(_) ->
|
t_to_packet(_) ->
|
||||||
Pkt = #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH,
|
Pkt = #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH,
|
||||||
qos = ?QOS_0,
|
qos = ?QOS_0,
|
||||||
|
|
@ -139,4 +142,3 @@ t_to_map(_) ->
|
||||||
{timestamp, emqx_message:timestamp(Msg)}],
|
{timestamp, emqx_message:timestamp(Msg)}],
|
||||||
?assertEqual(List, emqx_message:to_list(Msg)),
|
?assertEqual(List, emqx_message:to_list(Msg)),
|
||||||
?assertEqual(maps:from_list(List), emqx_message:to_map(Msg)).
|
?assertEqual(maps:from_list(List), emqx_message:to_map(Msg)).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ all() -> emqx_ct:all(?MODULE).
|
||||||
t_new(_) ->
|
t_new(_) ->
|
||||||
with_metrics_server(
|
with_metrics_server(
|
||||||
fun() ->
|
fun() ->
|
||||||
|
ok = emqx_metrics:new('metrics.test'),
|
||||||
ok = emqx_metrics:new('metrics.test'),
|
ok = emqx_metrics:new('metrics.test'),
|
||||||
0 = emqx_metrics:val('metrics.test'),
|
0 = emqx_metrics:val('metrics.test'),
|
||||||
ok = emqx_metrics:inc('metrics.test'),
|
ok = emqx_metrics:inc('metrics.test'),
|
||||||
|
|
@ -71,16 +72,70 @@ t_inc_recv(_) ->
|
||||||
with_metrics_server(
|
with_metrics_server(
|
||||||
fun() ->
|
fun() ->
|
||||||
ok = emqx_metrics:inc_recv(?PACKET(?CONNECT)),
|
ok = emqx_metrics:inc_recv(?PACKET(?CONNECT)),
|
||||||
?assertEqual(1, emqx_metrics:val('packets.received')),
|
ok = emqx_metrics:inc_recv(?PUBLISH_PACKET(0, 0)),
|
||||||
?assertEqual(1, emqx_metrics:val('packets.connect.received'))
|
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).
|
end).
|
||||||
|
|
||||||
t_inc_sent(_) ->
|
t_inc_sent(_) ->
|
||||||
with_metrics_server(
|
with_metrics_server(
|
||||||
fun() ->
|
fun() ->
|
||||||
ok = emqx_metrics:inc_sent(?CONNACK_PACKET(0)),
|
ok = emqx_metrics:inc_sent(?CONNACK_PACKET(0)),
|
||||||
?assertEqual(1, emqx_metrics:val('packets.sent')),
|
ok = emqx_metrics:inc_sent(?PUBLISH_PACKET(0, 0)),
|
||||||
?assertEqual(1, emqx_metrics:val('packets.connack.sent'))
|
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).
|
end).
|
||||||
|
|
||||||
t_trans(_) ->
|
t_trans(_) ->
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,10 @@ t_pipeline(_) ->
|
||||||
fun(_I, St) -> {ok, St+1} end,
|
fun(_I, St) -> {ok, St+1} end,
|
||||||
fun(I, St) -> {ok, I+1, St+1} end,
|
fun(I, St) -> {ok, I+1, St+1} end,
|
||||||
fun(I, St) -> {ok, I*2, St*2} 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(_) ->
|
t_start_timer(_) ->
|
||||||
TRef = emqx_misc:start_timer(1, tmsg),
|
TRef = emqx_misc:start_timer(1, tmsg),
|
||||||
|
|
@ -75,7 +78,8 @@ t_start_timer(_) ->
|
||||||
t_cancel_timer(_) ->
|
t_cancel_timer(_) ->
|
||||||
Timer = emqx_misc:start_timer(0, foo),
|
Timer = emqx_misc:start_timer(0, foo),
|
||||||
ok = emqx_misc:cancel_timer(Timer),
|
ok = emqx_misc:cancel_timer(Timer),
|
||||||
?assertEqual([], drain()).
|
?assertEqual([], drain()),
|
||||||
|
ok = emqx_misc:cancel_timer(undefined).
|
||||||
|
|
||||||
t_proc_name(_) ->
|
t_proc_name(_) ->
|
||||||
?assertEqual(emqx_pool_1, emqx_misc:proc_name(emqx_pool, 1)).
|
?assertEqual(emqx_pool_1, emqx_misc:proc_name(emqx_pool, 1)).
|
||||||
|
|
@ -84,7 +88,11 @@ t_proc_stats(_) ->
|
||||||
Pid1 = spawn(fun() -> exit(normal) end),
|
Pid1 = spawn(fun() -> exit(normal) end),
|
||||||
timer:sleep(10),
|
timer:sleep(10),
|
||||||
?assertEqual([], emqx_misc:proc_stats(Pid1)),
|
?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,
|
Pid2 ! msg,
|
||||||
timer:sleep(10),
|
timer:sleep(10),
|
||||||
?assertMatch([{mailbox_len, 1}|_], emqx_misc:proc_stats(Pid2)).
|
?assertMatch([{mailbox_len, 1}|_], emqx_misc:proc_stats(Pid2)).
|
||||||
|
|
@ -100,7 +108,24 @@ t_drain_down(_) ->
|
||||||
{Pid1, _Ref1} = erlang:spawn_monitor(fun() -> ok end),
|
{Pid1, _Ref1} = erlang:spawn_monitor(fun() -> ok end),
|
||||||
{Pid2, _Ref2} = erlang:spawn_monitor(fun() -> ok end),
|
{Pid2, _Ref2} = erlang:spawn_monitor(fun() -> ok end),
|
||||||
timer:sleep(100),
|
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() ->
|
||||||
drain([]).
|
drain([]).
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_mod_acl_internal_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
% t_load(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_unload(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_all_rules(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_check_acl(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_reload_acl(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_mod_presence_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
% t_load(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_unload(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_on_client_connected(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_on_client_disconnected(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_mod_rewrite_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
% t_load(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_rewrite_subscribe(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_rewrite_unsubscribe(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_rewrite_publish(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_unload(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_mod_subscription_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
% t_load(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_on_client_connected(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_unload(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
|
@ -52,6 +52,12 @@ end_per_suite(_Config) ->
|
||||||
%% Test cases
|
%% Test cases
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
% t_unload(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_load(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
%% Test case for emqx_mod_presence
|
%% Test case for emqx_mod_presence
|
||||||
t_mod_presence(_) ->
|
t_mod_presence(_) ->
|
||||||
ok = emqx_mod_presence:load([{qos, ?QOS_1}]),
|
ok = emqx_mod_presence:load([{qos, ?QOS_1}]),
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,12 @@
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
% t_get_caps(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_default(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
t_check_pub(_) ->
|
t_check_pub(_) ->
|
||||||
PubCaps = #{max_qos_allowed => ?QOS_1,
|
PubCaps = #{max_qos_allowed => ?QOS_1,
|
||||||
retain_available => false
|
retain_available => false
|
||||||
|
|
|
||||||
|
|
@ -78,3 +78,12 @@ t_validate_value(_) ->
|
||||||
foreach_prop(Fun) ->
|
foreach_prop(Fun) ->
|
||||||
lists:foreach(Fun, maps:to_list(emqx_mqtt_props:all())).
|
lists:foreach(Fun, maps:to_list(emqx_mqtt_props:all())).
|
||||||
|
|
||||||
|
|
||||||
|
% t_all(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_set(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_get(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
@ -28,6 +28,25 @@
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
|
||||||
|
% t_init(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_is_empty(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_len(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_max_len(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_dropped(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_stats(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
t_in(_) ->
|
t_in(_) ->
|
||||||
Opts = #{max_len => 5, store_qos0 => true},
|
Opts = #{max_len => 5, store_qos0 => true},
|
||||||
Q = ?Q:init(Opts),
|
Q = ?Q:init(Opts),
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,15 @@ init_per_suite(Config) ->
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
application:stop(os_mon).
|
application:stop(os_mon).
|
||||||
|
|
||||||
|
% t_set_mem_check_interval(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_set_sysmem_high_watermark(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_set_procmem_high_watermark(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
t_api(_) ->
|
t_api(_) ->
|
||||||
gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}),
|
gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}),
|
||||||
{ok, _} = emqx_os_mon:start_link([{cpu_check_interval, 1},
|
{ok, _} = emqx_os_mon:start_link([{cpu_check_interval, 1},
|
||||||
|
|
@ -47,11 +56,44 @@ t_api(_) ->
|
||||||
% timer:sleep(2000),
|
% timer:sleep(2000),
|
||||||
% ?assertEqual(true, lists:keymember(cpu_high_watermark, 1, alarm_handler:get_alarms())),
|
% ?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_high_watermark(0.8),
|
||||||
emqx_os_mon:set_cpu_low_watermark(0.75),
|
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.8, emqx_os_mon:get_cpu_high_watermark()),
|
||||||
?assertEqual(0.75, emqx_os_mon:get_cpu_low_watermark()),
|
?assertEqual(0.75, emqx_os_mon:get_cpu_low_watermark()),
|
||||||
% timer:sleep(3000),
|
% timer:sleep(3000),
|
||||||
% ?assertEqual(false, lists:keymember(cpu_high_watermark, 1, alarm_handler:get_alarms())),
|
% ?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.
|
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).
|
||||||
|
|
|
||||||
|
|
@ -82,17 +82,20 @@ t_check_publish(_) ->
|
||||||
Props = #{'Response-Topic' => <<"responsetopic">>, 'Topic-Alias' => 1},
|
Props = #{'Response-Topic' => <<"responsetopic">>, 'Topic-Alias' => 1},
|
||||||
ok = emqx_packet:check(?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, Props, <<"payload">>)),
|
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{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_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_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">>)),
|
{error, ?RC_TOPIC_ALIAS_INVALID} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Topic-Alias' => 0}, <<"payload">>)),
|
||||||
%% TODO::
|
%% TODO::
|
||||||
%% {error, ?RC_PROTOCOL_ERROR} = 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' => 10}, <<"payload">>)),
|
||||||
ok = 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">>)).
|
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Response-Topic' => <<"+/+">>}, <<"payload">>)).
|
||||||
|
|
||||||
t_check_subscribe(_) ->
|
t_check_subscribe(_) ->
|
||||||
ok = emqx_packet:check(?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => 1},
|
ok = emqx_packet:check(?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => 1},
|
||||||
[{<<"topic">>, #{qos => ?QOS_0}}])),
|
[{<<"topic">>, #{qos => ?QOS_0}}])),
|
||||||
|
{error, ?RC_TOPIC_FILTER_INVALID} = emqx_packet:check(#mqtt_packet_subscribe{topic_filters = []}),
|
||||||
{error, ?RC_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED} =
|
{error, ?RC_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED} =
|
||||||
emqx_packet:check(?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => -1},
|
emqx_packet:check(?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => -1},
|
||||||
[{<<"topic">>, #{qos => ?QOS_0, rp => 0}}])).
|
[{<<"topic">>, #{qos => ?QOS_0, rp => 0}}])).
|
||||||
|
|
@ -104,7 +107,11 @@ t_check_unsubscribe(_) ->
|
||||||
t_check_connect(_) ->
|
t_check_connect(_) ->
|
||||||
Opts = #{max_clientid_len => 5, mqtt_retain_available => false},
|
Opts = #{max_clientid_len => 5, mqtt_retain_available => false},
|
||||||
ok = emqx_packet:check(#mqtt_packet_connect{}, Opts),
|
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">>,
|
ConnPkt1 = #mqtt_packet_connect{proto_name = <<"MQIsdp">>,
|
||||||
proto_ver = ?MQTT_PROTO_V5
|
proto_ver = ?MQTT_PROTO_V5
|
||||||
},
|
},
|
||||||
|
|
@ -137,7 +144,9 @@ t_check_connect(_) ->
|
||||||
properties = #{'Request-Problem-Information' => 2}}), Opts),
|
properties = #{'Request-Problem-Information' => 2}}), Opts),
|
||||||
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(
|
{error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(
|
||||||
?CONNECT_PACKET(#mqtt_packet_connect{
|
?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(_) ->
|
t_from_to_message(_) ->
|
||||||
ExpectedMsg = emqx_message:make(<<"clientid">>, ?QOS_0, <<"topic">>, <<"payload">>),
|
ExpectedMsg = emqx_message:make(<<"clientid">>, ?QOS_0, <<"topic">>, <<"payload">>),
|
||||||
|
|
@ -160,6 +169,7 @@ t_from_to_message(_) ->
|
||||||
}).
|
}).
|
||||||
|
|
||||||
t_will_msg(_) ->
|
t_will_msg(_) ->
|
||||||
|
?assertEqual(undefined, emqx_packet:will_msg(#mqtt_packet_connect{will_flag = false})),
|
||||||
Pkt = #mqtt_packet_connect{will_flag = true,
|
Pkt = #mqtt_packet_connect{will_flag = true,
|
||||||
clientid = <<"clientid">>,
|
clientid = <<"clientid">>,
|
||||||
username = "test",
|
username = "test",
|
||||||
|
|
@ -171,10 +181,30 @@ t_will_msg(_) ->
|
||||||
},
|
},
|
||||||
Msg = emqx_packet:will_msg(Pkt),
|
Msg = emqx_packet:will_msg(Pkt),
|
||||||
?assertEqual(<<"clientid">>, Msg#message.from),
|
?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_format(_) ->
|
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(?CONNACK_PACKET(?CONNACK_SERVER))]),
|
||||||
io:format("~s", [emqx_packet:format(?PUBLISH_PACKET(?QOS_1, 1))]),
|
io:format("~s", [emqx_packet:format(?PUBLISH_PACKET(?QOS_1, 1))]),
|
||||||
io:format("~s", [emqx_packet:format(?PUBLISH_PACKET(?QOS_2, <<"topic">>, 10, <<"payload">>))]),
|
io:format("~s", [emqx_packet:format(?PUBLISH_PACKET(?QOS_2, <<"topic">>, 10, <<"payload">>))]),
|
||||||
|
|
@ -183,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(?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(?SUBACK_PACKET(40, [?QOS_0, ?QOS_1]))]),
|
||||||
io:format("~s", [emqx_packet:format(?UNSUBSCRIBE_PACKET(89, [<<"t">>, <<"t2">>]))]),
|
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))]).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
t_update_counter(_) ->
|
t_update_counter(_) ->
|
||||||
?assertEqual(undefined, emqx_pd:update_counter(bytes, 1)),
|
?assertEqual(undefined, emqx_pd:inc_counter(bytes, 1)),
|
||||||
?assertEqual(1, emqx_pd:update_counter(bytes, 1)),
|
?assertEqual(1, emqx_pd:inc_counter(bytes, 1)),
|
||||||
?assertEqual(2, emqx_pd:update_counter(bytes, 1)),
|
?assertEqual(2, emqx_pd:inc_counter(bytes, 1)),
|
||||||
?assertEqual(3, emqx_pd:get_counter(bytes)),
|
?assertEqual(3, emqx_pd:get_counter(bytes)),
|
||||||
?assertEqual(3, emqx_pd:reset_counter(bytes)),
|
?assertEqual(3, emqx_pd:reset_counter(bytes)),
|
||||||
?assertEqual(0, emqx_pd:get_counter(bytes)).
|
?assertEqual(0, emqx_pd:get_counter(bytes)).
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
|
|
||||||
%% Compile extra plugin code
|
%% Compile extra plugin code
|
||||||
|
|
@ -42,6 +43,21 @@ init_per_suite(Config) ->
|
||||||
|
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
|
% t_load_expand_plugin(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_list(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_find_plugin(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_unload(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_init(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
set_sepecial_cfg(_) ->
|
set_sepecial_cfg(_) ->
|
||||||
ExpandPath = filename:dirname(code:lib_dir(emqx_mini_plugin)),
|
ExpandPath = filename:dirname(code:lib_dir(emqx_mini_plugin)),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
% t_new(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_count(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
t_monitor(_) ->
|
t_monitor(_) ->
|
||||||
PMon = emqx_pmon:new(),
|
PMon = emqx_pmon:new(),
|
||||||
PMon1 = emqx_pmon:monitor(self(), PMon),
|
PMon1 = emqx_pmon:monitor(self(), PMon),
|
||||||
|
|
@ -31,6 +37,9 @@ t_monitor(_) ->
|
||||||
PMon2 = emqx_pmon:demonitor(self(), PMon2),
|
PMon2 = emqx_pmon:demonitor(self(), PMon2),
|
||||||
?assertEqual(0, emqx_pmon:count(PMon2)).
|
?assertEqual(0, emqx_pmon:count(PMon2)).
|
||||||
|
|
||||||
|
% t_demonitor(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
t_find(_) ->
|
t_find(_) ->
|
||||||
PMon = emqx_pmon:new(),
|
PMon = emqx_pmon:new(),
|
||||||
PMon1 = emqx_pmon:monitor(self(), val, PMon),
|
PMon1 = emqx_pmon:monitor(self(), val, PMon),
|
||||||
|
|
@ -51,3 +60,5 @@ t_erase(_) ->
|
||||||
?assertEqual([{self(), val}], Items),
|
?assertEqual([{self(), val}], Items),
|
||||||
?assertEqual(0, emqx_pmon:count(PMon3)).
|
?assertEqual(0, emqx_pmon:count(PMon3)).
|
||||||
|
|
||||||
|
% t_erase_all(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
|
||||||
|
|
@ -86,3 +86,5 @@ t_unexpected(_) ->
|
||||||
test_mfa() ->
|
test_mfa() ->
|
||||||
lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]).
|
lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]).
|
||||||
|
|
||||||
|
% t_async_submit(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
|
||||||
|
|
@ -26,99 +26,152 @@
|
||||||
|
|
||||||
all() -> emqx_ct:all(?SUITE).
|
all() -> emqx_ct:all(?SUITE).
|
||||||
|
|
||||||
t_priority_queue_plen(_) ->
|
t_is_queue(_) ->
|
||||||
Q = ?PQ:new(),
|
Q = ?PQ:new(),
|
||||||
0 = ?PQ:plen(0, Q),
|
?assertEqual(true, ?PQ:is_queue(Q)),
|
||||||
Q0 = ?PQ:in(z, Q),
|
Q1 = ?PQ:in(a, 1, Q),
|
||||||
1 = ?PQ:plen(0, Q0),
|
?assertEqual(true, ?PQ:is_queue(Q1)),
|
||||||
Q1 = ?PQ:in(x, 1, Q0),
|
?assertEqual(false, ?PQ:is_queue(bad_queue)).
|
||||||
1 = ?PQ:plen(1, Q1),
|
|
||||||
Q2 = ?PQ:in(y, 2, Q1),
|
|
||||||
1 = ?PQ:plen(2, Q2),
|
|
||||||
Q3 = ?PQ:in(z, 2, Q2),
|
|
||||||
2 = ?PQ:plen(2, Q3),
|
|
||||||
{_, Q4} = ?PQ:out(1, Q3),
|
|
||||||
0 = ?PQ:plen(1, Q4),
|
|
||||||
{_, Q5} = ?PQ:out(Q4),
|
|
||||||
1 = ?PQ:plen(2, Q5),
|
|
||||||
{_, Q6} = ?PQ:out(Q5),
|
|
||||||
0 = ?PQ:plen(2, Q6),
|
|
||||||
1 = ?PQ:len(Q6),
|
|
||||||
{_, Q7} = ?PQ:out(Q6),
|
|
||||||
0 = ?PQ:len(Q7).
|
|
||||||
|
|
||||||
t_priority_queue_out2(_) ->
|
t_is_empty(_) ->
|
||||||
Els = [a, {b, 1}, {c, 1}, {d, 2}, {e, 2}, {f, 2}],
|
|
||||||
Q = ?PQ:new(),
|
Q = ?PQ:new(),
|
||||||
Q0 = lists:foldl(
|
?assertEqual(true, ?PQ:is_empty(Q)),
|
||||||
|
?assertEqual(false, ?PQ:is_empty(?PQ:in(a, Q))).
|
||||||
|
|
||||||
|
t_len(_) ->
|
||||||
|
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(_) ->
|
||||||
|
Q = ?PQ:new(),
|
||||||
|
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_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) ->
|
fun({El, P}, Acc) ->
|
||||||
?PQ:in(El, P, Acc);
|
?PQ:in(El, P, Acc);
|
||||||
(El, Acc) ->
|
(El, Acc) ->
|
||||||
?PQ:in(El, Acc)
|
?PQ:in(El, Acc)
|
||||||
end, Q, Els),
|
end, Q, Els),
|
||||||
{Val, Q1} = ?PQ:out(Q0),
|
?assertEqual({pqueue, [{infinity, {queue, [e], [], 1}},
|
||||||
{value, d} = Val,
|
{-2, {queue, [f], [], 1}},
|
||||||
{Val1, Q2} = ?PQ:out(2, Q1),
|
{-1, {queue, [d], [c], 2}},
|
||||||
{value, e} = Val1,
|
{0, {queue, [b], [a], 2}}]}, Q1).
|
||||||
{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).
|
|
||||||
|
|
||||||
t_priority_queues(_) ->
|
t_out(_) ->
|
||||||
Q0 = ?PQ:new(),
|
Q = ?PQ:new(),
|
||||||
Q1 = ?PQ:new(),
|
{empty, Q} = ?PQ:out(Q),
|
||||||
PQueue = {pqueue, [{0, Q0}, {1, Q1}]},
|
{empty, Q} = ?PQ:out(0, Q),
|
||||||
?assert(?PQ:is_queue(PQueue)),
|
try ?PQ:out(1, Q) of
|
||||||
[] = ?PQ:to_list(PQueue),
|
_ -> 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()),
|
t_out_2(_) ->
|
||||||
PQueue2 = ?PQ:in(b, 0, PQueue1),
|
{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),
|
t_out_p(_) ->
|
||||||
PQueue4 = ?PQ:in(d, 1, PQueue3),
|
{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),
|
Q1 = ?PQ:in(a, ?PQ:new()),
|
||||||
PQueue4 = ?PQ:from_list([{1, c}, {1, d}, {0, a}, {0, b}]),
|
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()),
|
empty = ?PQ:highest(?PQ:new()),
|
||||||
0 = ?PQ:highest(PQueue1),
|
0 = ?PQ:highest(?PQ:from_list([{0, a}, {0, b}])),
|
||||||
1 = ?PQ:highest(PQueue4),
|
2 = ?PQ:highest(?PQ:from_list([{0, a}, {0, b}, {1, c}, {2, d}, {2, e}])).
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
|
|
@ -25,6 +25,24 @@
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
% t_name(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_text(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_mqtt_frame_error(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_connack_error(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_compat(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_formalized(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
t_prop_name_text(_) ->
|
t_prop_name_text(_) ->
|
||||||
?assert(proper:quickcheck(prop_name_text(), prop_name_text(opts))).
|
?assert(proper:quickcheck(prop_name_text(), prop_name_text(opts))).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,24 @@ t_mnesia(_) ->
|
||||||
%% for coverage
|
%% for coverage
|
||||||
ok = emqx_router:mnesia(copy).
|
ok = emqx_router:mnesia(copy).
|
||||||
|
|
||||||
|
% t_add_route(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_do_add_route(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_lookup_routes(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_delete_route(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_do_delete_route(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_topics(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
t_add_delete(_) ->
|
t_add_delete(_) ->
|
||||||
?R:add_route(<<"a/b/c">>),
|
?R:add_route(<<"a/b/c">>),
|
||||||
?R:add_route(<<"a/b/c">>, node()),
|
?R:add_route(<<"a/b/c">>, node()),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_router_helper_SUITE).
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
end_per_testcase(_TestCase, Config) ->
|
||||||
|
Config.
|
||||||
|
|
||||||
|
% t_mnesia(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_monitor(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_stats_fun(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
|
@ -31,7 +31,9 @@ t_prop_rpc(_) ->
|
||||||
ok = application:set_env(gen_rpc, call_receive_timeout, 1),
|
ok = application:set_env(gen_rpc, call_receive_timeout, 1),
|
||||||
ok = emqx_logger:set_log_level(emergency),
|
ok = emqx_logger:set_log_level(emergency),
|
||||||
?assert(proper:quickcheck(prop_node(), Opts)),
|
?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(), Opts)),
|
||||||
|
?assert(proper:quickcheck(prop_nodes_with_key(), Opts)),
|
||||||
ok = application:stop(gen_rpc),
|
ok = application:stop(gen_rpc),
|
||||||
ok = unload().
|
ok = unload().
|
||||||
|
|
||||||
|
|
@ -46,6 +48,17 @@ prop_node() ->
|
||||||
end
|
end
|
||||||
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() ->
|
prop_nodes() ->
|
||||||
?FORALL(Nodes, nodesname(),
|
?FORALL(Nodes, nodesname(),
|
||||||
begin
|
begin
|
||||||
|
|
@ -59,6 +72,19 @@ prop_nodes() ->
|
||||||
end
|
end
|
||||||
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
|
%% helper
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
@ -85,8 +111,19 @@ nodename() ->
|
||||||
list_to_atom(Node)
|
list_to_atom(Node)
|
||||||
end).
|
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() ->
|
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() ->
|
node_prefix() ->
|
||||||
oneof(["emqxct", text_like()]).
|
oneof(["emqxct", text_like()]).
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,21 @@
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
% t_currval(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_delete(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_create(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_reclaim(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_nextval(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
t_generate(_) ->
|
t_generate(_) ->
|
||||||
ok = emqx_sequence:create(seqtab),
|
ok = emqx_sequence:create(seqtab),
|
||||||
?assertEqual(0, currval(seqtab, key)),
|
?assertEqual(0, currval(seqtab, key)),
|
||||||
|
|
|
||||||
|
|
@ -23,304 +23,240 @@
|
||||||
-include_lib("proper/include/proper.hrl").
|
-include_lib("proper/include/proper.hrl").
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
-define(mock_modules,
|
-import(emqx_session, [set_field/3]).
|
||||||
[ emqx_metrics
|
|
||||||
, emqx_broker
|
|
||||||
, emqx_misc
|
|
||||||
, emqx_message
|
|
||||||
, emqx_hooks
|
|
||||||
, emqx_zone
|
|
||||||
]).
|
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
t_proper_session(_) ->
|
%%--------------------------------------------------------------------
|
||||||
Opts = [{numtests, 100}, {to_file, user}],
|
%% CT callbacks
|
||||||
ok = emqx_logger:set_log_level(emergency),
|
%%--------------------------------------------------------------------
|
||||||
ok = before_proper(),
|
|
||||||
?assert(proper:quickcheck(prop_session(), Opts)),
|
|
||||||
ok = after_proper().
|
|
||||||
|
|
||||||
before_proper() ->
|
init_per_testcase(_TestCase, Config) ->
|
||||||
load(?mock_modules).
|
%% Meck Broker
|
||||||
|
ok = meck:new(emqx_broker, [passthrough, no_history]),
|
||||||
|
ok = meck:new(emqx_hooks, [passthrough, no_history]),
|
||||||
|
ok = meck:expect(emqx_hooks, run, fun(_Hook, _Args) -> ok end),
|
||||||
|
Config.
|
||||||
|
|
||||||
after_proper() ->
|
end_per_testcase(_TestCase, Config) ->
|
||||||
unload(?mock_modules),
|
ok = meck:unload(emqx_broker),
|
||||||
emqx_logger:set_log_level(error).
|
ok = meck:unload(emqx_hooks),
|
||||||
|
Config.
|
||||||
|
|
||||||
prop_session() ->
|
%%--------------------------------------------------------------------
|
||||||
?FORALL({Session, OpList}, {session(), session_op_list()},
|
%% Test cases for session init
|
||||||
begin
|
%%--------------------------------------------------------------------
|
||||||
try
|
|
||||||
apply_ops(Session, OpList),
|
|
||||||
true
|
|
||||||
after
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end).
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%
|
t_session_init(_) ->
|
||||||
%%% Helpers %%%
|
Session = emqx_session:init(#{zone => zone}, #{receive_maximum => 64}),
|
||||||
%%%%%%%%%%%%%%%
|
?assertEqual(#{}, emqx_session:info(subscriptions, Session)),
|
||||||
|
?assertEqual(0, emqx_session:info(subscriptions_cnt, Session)),
|
||||||
|
?assertEqual(0, emqx_session:info(subscriptions_max, Session)),
|
||||||
|
?assertEqual(false, emqx_session:info(upgrade_qos, Session)),
|
||||||
|
?assertEqual(0, emqx_session:info(inflight_cnt, Session)),
|
||||||
|
?assertEqual(64, emqx_session:info(inflight_max, Session)),
|
||||||
|
?assertEqual(1, emqx_session:info(next_pkt_id, Session)),
|
||||||
|
?assertEqual(0, emqx_session:info(retry_interval, Session)),
|
||||||
|
?assertEqual(0, emqx_session:info(awaiting_rel_cnt, Session)),
|
||||||
|
?assertEqual(100, emqx_session:info(awaiting_rel_max, Session)),
|
||||||
|
?assertEqual(3600000, emqx_session:info(awaiting_rel_timeout, Session)),
|
||||||
|
?assert(is_integer(emqx_session:info(created_at, Session))).
|
||||||
|
|
||||||
apply_ops(Session, []) ->
|
%%--------------------------------------------------------------------
|
||||||
?assertEqual(session, element(1, Session));
|
%% Test cases for session info/stats
|
||||||
apply_ops(Session, [Op | Rest]) ->
|
%%--------------------------------------------------------------------
|
||||||
NSession = apply_op(Session, Op),
|
|
||||||
apply_ops(NSession, Rest).
|
|
||||||
|
|
||||||
apply_op(Session, info) ->
|
t_session_info(_) ->
|
||||||
Info = emqx_session:info(Session),
|
Info = emqx_session:info(session()),
|
||||||
?assert(is_map(Info)),
|
?assertMatch(#{subscriptions := #{},
|
||||||
?assert(maps:size(Info) > 0),
|
subscriptions_max := 0,
|
||||||
Session;
|
upgrade_qos := false,
|
||||||
apply_op(Session, attrs) ->
|
inflight_max := 0,
|
||||||
Attrs = emqx_session:attrs(Session),
|
retry_interval := 0,
|
||||||
?assert(is_map(Attrs)),
|
mqueue_len := 0,
|
||||||
?assert(maps:size(Attrs) > 0),
|
mqueue_max := 1000,
|
||||||
Session;
|
mqueue_dropped := 0,
|
||||||
apply_op(Session, stats) ->
|
next_pkt_id := 1,
|
||||||
Stats = emqx_session:stats(Session),
|
awaiting_rel := #{},
|
||||||
?assert(is_list(Stats)),
|
awaiting_rel_max := 100,
|
||||||
?assert(length(Stats) > 0),
|
awaiting_rel_timeout := 3600000
|
||||||
Session;
|
}, Info).
|
||||||
apply_op(Session, {info, InfoArg}) ->
|
|
||||||
_Ret = emqx_session:info(InfoArg, Session),
|
|
||||||
Session;
|
|
||||||
apply_op(Session, {subscribe, {Client, TopicFilter, SubOpts}}) ->
|
|
||||||
case emqx_session:subscribe(Client, TopicFilter, SubOpts, Session) of
|
|
||||||
{ok, NSession} ->
|
|
||||||
NSession;
|
|
||||||
{error, ?RC_QUOTA_EXCEEDED} ->
|
|
||||||
Session
|
|
||||||
end;
|
|
||||||
apply_op(Session, {unsubscribe, {Client, TopicFilter}}) ->
|
|
||||||
case emqx_session:unsubscribe(Client, TopicFilter, Session) of
|
|
||||||
{ok, NSession} ->
|
|
||||||
NSession;
|
|
||||||
{error, ?RC_NO_SUBSCRIPTION_EXISTED} ->
|
|
||||||
Session
|
|
||||||
end;
|
|
||||||
apply_op(Session, {publish, {PacketId, Msg}}) ->
|
|
||||||
case emqx_session:publish(PacketId, Msg, Session) of
|
|
||||||
{ok, _Msg} ->
|
|
||||||
Session;
|
|
||||||
{ok, _Deliver, NSession} ->
|
|
||||||
NSession;
|
|
||||||
{error, _ErrorCode} ->
|
|
||||||
Session
|
|
||||||
end;
|
|
||||||
apply_op(Session, {puback, PacketId}) ->
|
|
||||||
case emqx_session:puback(PacketId, Session) of
|
|
||||||
{ok, _Msg, NSession} ->
|
|
||||||
NSession;
|
|
||||||
{ok, _Msg, _Publishes, NSession} ->
|
|
||||||
NSession;
|
|
||||||
{error, _ErrorCode} ->
|
|
||||||
Session
|
|
||||||
end;
|
|
||||||
apply_op(Session, {pubrec, PacketId}) ->
|
|
||||||
case emqx_session:pubrec(PacketId, Session) of
|
|
||||||
{ok, _Msg, NSession} ->
|
|
||||||
NSession;
|
|
||||||
{error, _ErrorCode} ->
|
|
||||||
Session
|
|
||||||
end;
|
|
||||||
apply_op(Session, {pubrel, PacketId}) ->
|
|
||||||
case emqx_session:pubrel(PacketId, Session) of
|
|
||||||
{ok, NSession} ->
|
|
||||||
NSession;
|
|
||||||
{error, _ErrorCode} ->
|
|
||||||
Session
|
|
||||||
end;
|
|
||||||
apply_op(Session, {pubcomp, PacketId}) ->
|
|
||||||
case emqx_session:pubcomp(PacketId, Session) of
|
|
||||||
{ok, _Msgs} ->
|
|
||||||
Session;
|
|
||||||
{ok, _Msgs, NSession} ->
|
|
||||||
NSession;
|
|
||||||
{error, _ErrorCode} ->
|
|
||||||
Session
|
|
||||||
end;
|
|
||||||
apply_op(Session, {deliver, Delivers}) ->
|
|
||||||
{ok, _Msgs, NSession} = emqx_session:deliver(Delivers, Session),
|
|
||||||
NSession.
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%
|
t_session_attrs(_) ->
|
||||||
%%% Generators %%%
|
Attrs = emqx_session:attrs(session()),
|
||||||
%%%%%%%%%%%%%%%%%%
|
io:format("~p~n", [Attrs]).
|
||||||
session_op_list() ->
|
|
||||||
Union = [info,
|
|
||||||
attrs,
|
|
||||||
stats,
|
|
||||||
{info, info_args()},
|
|
||||||
{subscribe, sub_args()},
|
|
||||||
{unsubscribe, unsub_args()},
|
|
||||||
{publish, publish_args()},
|
|
||||||
{puback, puback_args()},
|
|
||||||
{pubrec, pubrec_args()},
|
|
||||||
{pubrel, pubrel_args()},
|
|
||||||
{pubcomp, pubcomp_args()},
|
|
||||||
{deliver, deliver_args()}
|
|
||||||
],
|
|
||||||
list(?LAZY(oneof(Union))).
|
|
||||||
|
|
||||||
deliver_args() ->
|
t_session_stats(_) ->
|
||||||
list({deliver, topic(), message()}).
|
Stats = emqx_session:stats(session()),
|
||||||
|
io:format("~p~n", [Stats]).
|
||||||
|
|
||||||
info_args() ->
|
%%--------------------------------------------------------------------
|
||||||
oneof([subscriptions,
|
%% Test cases for pub/sub
|
||||||
subscriptions_max,
|
%%--------------------------------------------------------------------
|
||||||
upgrade_qos,
|
|
||||||
inflight,
|
|
||||||
inflight_max,
|
|
||||||
retry_interval,
|
|
||||||
mqueue_len,
|
|
||||||
mqueue_max,
|
|
||||||
mqueue_dropped,
|
|
||||||
next_pkt_id,
|
|
||||||
awaiting_rel,
|
|
||||||
awaiting_rel_max,
|
|
||||||
await_rel_timeout,
|
|
||||||
created_at
|
|
||||||
]).
|
|
||||||
|
|
||||||
sub_args() ->
|
t_subscribe(_) ->
|
||||||
?LET({ClientId, TopicFilter, SubOpts},
|
ok = meck:expect(emqx_broker, subscribe, fun(_, _, _) -> ok end),
|
||||||
{clientid(), topic(), sub_opts()},
|
ok = meck:expect(emqx_broker, set_subopts, fun(_, _) -> ok end),
|
||||||
{#{clientid => ClientId}, TopicFilter, SubOpts}).
|
{ok, Session} = emqx_session:subscribe(
|
||||||
|
clientinfo(), <<"#">>, subopts(), session()),
|
||||||
|
?assertEqual(1, emqx_session:info(subscriptions_cnt, Session)).
|
||||||
|
|
||||||
unsub_args() ->
|
t_is_subscriptions_full_false(_) ->
|
||||||
?LET({ClientId, TopicFilter},
|
Session = session(#{max_subscriptions => 0}),
|
||||||
{clientid(), topic()},
|
?assertNot(emqx_session:is_subscriptions_full(Session)).
|
||||||
{#{clientid => ClientId}, TopicFilter}).
|
|
||||||
|
|
||||||
publish_args() ->
|
t_is_subscriptions_full_true(_) ->
|
||||||
?LET({PacketId, Message},
|
Session = session(#{max_subscriptions => 1}),
|
||||||
{packetid(), message()},
|
?assertNot(emqx_session:is_subscriptions_full(Session)),
|
||||||
{PacketId, Message}).
|
Subs = #{<<"t1">> => subopts(), <<"t2">> => subopts()},
|
||||||
|
NSession = set_field(subscriptions, Subs, Session),
|
||||||
|
?assert(emqx_session:is_subscriptions_full(NSession)).
|
||||||
|
|
||||||
puback_args() ->
|
t_unsubscribe(_) ->
|
||||||
packetid().
|
ok = meck:expect(emqx_broker, unsubscribe, fun(_) -> ok end),
|
||||||
|
Session = session(#{subscriptions => #{<<"#">> => subopts()}}),
|
||||||
|
{ok, NSession} = emqx_session:unsubscribe(clientinfo(), <<"#">>, Session),
|
||||||
|
Error = emqx_session:unsubscribe(clientinfo(), <<"#">>, NSession),
|
||||||
|
?assertEqual({error, ?RC_NO_SUBSCRIPTION_EXISTED}, Error).
|
||||||
|
|
||||||
pubrec_args() ->
|
t_publish_qos2(_) ->
|
||||||
packetid().
|
ok = meck:expect(emqx_broker, publish, fun(_) -> [] end),
|
||||||
|
Msg = emqx_message:make(test, ?QOS_2, <<"t">>, <<"payload">>),
|
||||||
|
{ok, [], Session} = emqx_session:publish(1, Msg, session()),
|
||||||
|
?assertEqual(1, emqx_session:info(awaiting_rel_cnt, Session)).
|
||||||
|
|
||||||
pubrel_args() ->
|
t_publish_qos1(_) ->
|
||||||
packetid().
|
ok = meck:expect(emqx_broker, publish, fun(_) -> [] end),
|
||||||
|
Msg = emqx_message:make(test, ?QOS_1, <<"t">>, <<"payload">>),
|
||||||
|
{ok, [], Session} = emqx_session:publish(1, Msg, session()).
|
||||||
|
|
||||||
pubcomp_args() ->
|
t_publish_qos0(_) ->
|
||||||
packetid().
|
ok = meck:expect(emqx_broker, publish, fun(_) -> [] end),
|
||||||
|
Msg = emqx_message:make(test, ?QOS_1, <<"t">>, <<"payload">>),
|
||||||
|
{ok, [], Session} = emqx_session:publish(0, Msg, session()).
|
||||||
|
|
||||||
sub_opts() ->
|
t_is_awaiting_full_false(_) ->
|
||||||
?LET({RH, RAP, NL, QOS, SHARE, SUBID},
|
?assertNot(emqx_session:is_awaiting_full(session(#{max_awaiting_rel => 0}))).
|
||||||
{rh(), rap(), nl(), qos(), share(), subid()}
|
|
||||||
, make_subopts(RH, RAP, NL, QOS, SHARE, SUBID)).
|
|
||||||
|
|
||||||
message() ->
|
t_is_awaiting_full_true(_) ->
|
||||||
?LET({QoS, Topic, Payload},
|
Session = session(#{max_awaiting_rel => 1,
|
||||||
{qos(), topic(), payload()},
|
awaiting_rel => #{1 => 1}
|
||||||
emqx_message:make(proper, QoS, Topic, Payload)).
|
}),
|
||||||
|
?assert(emqx_session:is_awaiting_full(Session)).
|
||||||
|
|
||||||
subid() -> integer().
|
t_puback(_) ->
|
||||||
|
Msg = emqx_message:make(test, ?QOS_1, <<"t">>, <<>>),
|
||||||
|
Inflight = emqx_inflight:insert(1, {Msg, os:timestamp()}, emqx_inflight:new()),
|
||||||
|
Session = set_field(inflight, Inflight, session()),
|
||||||
|
{ok, Msg, NSession} = emqx_session:puback(1, Session),
|
||||||
|
?assertEqual(0, emqx_session:info(inflight_cnt, NSession)).
|
||||||
|
|
||||||
rh() -> oneof([0, 1, 2]).
|
t_puback_error_packet_id_in_use(_) ->
|
||||||
|
Inflight = emqx_inflight:insert(1, {pubrel, os:timestamp()}, emqx_inflight:new()),
|
||||||
|
Session = set_field(inflight, Inflight, session()),
|
||||||
|
{error, ?RC_PACKET_IDENTIFIER_IN_USE} = emqx_session:puback(1, Session).
|
||||||
|
|
||||||
rap() -> oneof([0, 1]).
|
t_puback_error_packet_id_not_found(_) ->
|
||||||
|
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} = emqx_session:puback(1, session()).
|
||||||
|
|
||||||
nl() -> oneof([0, 1]).
|
t_pubrec(_) ->
|
||||||
|
Msg = emqx_message:make(test, ?QOS_2, <<"t">>, <<>>),
|
||||||
|
Inflight = emqx_inflight:insert(2, {Msg, os:timestamp()}, emqx_inflight:new()),
|
||||||
|
Session = set_field(inflight, Inflight, session()),
|
||||||
|
{ok, Msg, NSession} = emqx_session:pubrec(2, Session),
|
||||||
|
?assertMatch([{pubrel, _}], emqx_inflight:values(emqx_session:info(inflight, NSession))).
|
||||||
|
|
||||||
qos() -> oneof([0, 1, 2]).
|
t_pubrec_packet_id_in_use_error(_) ->
|
||||||
|
Inflight = emqx_inflight:insert(1, {pubrel, ts()}, emqx_inflight:new()),
|
||||||
|
Session = set_field(inflight, Inflight, session()),
|
||||||
|
{error, ?RC_PACKET_IDENTIFIER_IN_USE} = emqx_session:puback(1, Session).
|
||||||
|
|
||||||
share() -> binary().
|
t_pubrec_packet_id_not_found_error(_) ->
|
||||||
|
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} = emqx_session:pubrec(1, session()).
|
||||||
|
|
||||||
clientid() -> binary().
|
t_pubrel(_) ->
|
||||||
|
Session = set_field(awaiting_rel, #{1 => os:timestamp()}, session()),
|
||||||
|
{ok, NSession} = emqx_session:pubrel(1, Session),
|
||||||
|
?assertEqual(#{}, emqx_session:info(awaiting_rel, NSession)).
|
||||||
|
|
||||||
topic() -> ?LET(No, choose(1, 10),
|
t_pubrel_id_not_found(_) ->
|
||||||
begin
|
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} = emqx_session:pubrel(1, session()).
|
||||||
NoBin = integer_to_binary(No),
|
|
||||||
<<"topic/", NoBin/binary>>
|
|
||||||
end).
|
|
||||||
|
|
||||||
payload() -> binary().
|
t_pubcomp(_) ->
|
||||||
|
Inflight = emqx_inflight:insert(2, {pubrel, os:timestamp()}, emqx_inflight:new()),
|
||||||
|
Session = emqx_session:set_field(inflight, Inflight, session()),
|
||||||
|
{ok, NSession} = emqx_session:pubcomp(2, Session),
|
||||||
|
?assertEqual(0, emqx_session:info(inflight_cnt, NSession)).
|
||||||
|
|
||||||
packetid() -> choose(1, 30).
|
t_pubcomp_id_not_found(_) ->
|
||||||
|
{error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} = emqx_session:pubcomp(2, session()).
|
||||||
|
|
||||||
zone() ->
|
%%--------------------------------------------------------------------
|
||||||
?LET(Zone, [{max_subscriptions, max_subscription()},
|
%% Test cases for deliver/retry
|
||||||
{upgrade_qos, upgrade_qos()},
|
%%--------------------------------------------------------------------
|
||||||
{retry_interval, retry_interval()},
|
|
||||||
{max_awaiting_rel, max_awaiting_rel()},
|
|
||||||
{await_rel_timeout, await_rel_timeout()}]
|
|
||||||
, maps:from_list(Zone)).
|
|
||||||
|
|
||||||
max_subscription() ->
|
t_dequeue(_) ->
|
||||||
frequency([{33, 0},
|
{ok, Session} = emqx_session:dequeue(session()).
|
||||||
{33, 1},
|
|
||||||
{34, choose(0,10)}]).
|
|
||||||
|
|
||||||
upgrade_qos() -> bool().
|
t_deliver(_) ->
|
||||||
|
Delivers = [delivery(?QOS_1, <<"t1">>), delivery(?QOS_2, <<"t2">>)],
|
||||||
|
{ok, Publishes, _Session} = emqx_session:deliver(Delivers, session()),
|
||||||
|
?assertEqual(2, length(Publishes)).
|
||||||
|
|
||||||
retry_interval() -> ?LET(Interval, choose(0, 20), Interval*1000).
|
t_enqueue(_) ->
|
||||||
|
Delivers = [delivery(?QOS_1, <<"t1">>), delivery(?QOS_2, <<"t2">>)],
|
||||||
|
Session = emqx_session:enqueue(Delivers, session()),
|
||||||
|
?assertEqual(2, emqx_session:info(mqueue_len, Session)).
|
||||||
|
|
||||||
max_awaiting_rel() -> choose(0, 10).
|
t_retry(_) ->
|
||||||
|
{ok, _Session} = emqx_session:retry(session()).
|
||||||
|
|
||||||
await_rel_timeout() -> ?LET(Interval, choose(0, 150), Interval*1000).
|
%%--------------------------------------------------------------------
|
||||||
|
%% Test cases for takeover/resume
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
max_inflight() -> choose(0, 10).
|
t_takeover(_) ->
|
||||||
|
ok = meck:expect(emqx_broker, unsubscribe, fun(_) -> ok end),
|
||||||
|
Session = session(#{subscriptions => #{<<"t">> => ?DEFAULT_SUBOPTS}}),
|
||||||
|
ok = emqx_session:takeover(Session).
|
||||||
|
|
||||||
option() ->
|
t_resume(_) ->
|
||||||
?LET(Option, [{receive_maximum , max_inflight()}],
|
ok = meck:expect(emqx_broker, subscribe, fun(_, _, _) -> ok end),
|
||||||
maps:from_list(Option)).
|
Subs = #{<<"t">> => ?DEFAULT_SUBOPTS},
|
||||||
|
Session = session(#{subscriptions => #{<<"t">> => ?DEFAULT_SUBOPTS}}),
|
||||||
|
ok = emqx_session:resume(<<"clientid">>, Session).
|
||||||
|
|
||||||
session() ->
|
t_redeliver(_) ->
|
||||||
?LET({Zone, Options},
|
{ok, [], _Session} = emqx_session:redeliver(session()).
|
||||||
{zone(), option()},
|
|
||||||
begin
|
|
||||||
Session = emqx_session:init(#{zone => Zone}, Options),
|
|
||||||
emqx_session:set_field(next_pkt_id, 16#ffff, Session)
|
|
||||||
end).
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%
|
t_expire(_) ->
|
||||||
%%% Internal functions %%%
|
{ok, _Session} = emqx_session:expire(awaiting_rel, session()).
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
||||||
|
|
||||||
make_subopts(RH, RAP, NL, QOS, SHARE, SubId) ->
|
%%--------------------------------------------------------------------
|
||||||
#{rh => RH,
|
%% Helper functions
|
||||||
rap => RAP,
|
%%--------------------------------------------------------------------
|
||||||
nl => NL,
|
|
||||||
qos => QOS,
|
session() -> session(#{}).
|
||||||
share => SHARE,
|
session(InitFields) when is_map(InitFields) ->
|
||||||
subid => SubId}.
|
maps:fold(fun(Field, Value, Session) ->
|
||||||
|
emqx_session:set_field(Field, Value, Session)
|
||||||
|
end,
|
||||||
|
emqx_session:init(#{zone => zone}, #{receive_maximum => 0}),
|
||||||
|
InitFields).
|
||||||
|
|
||||||
|
|
||||||
load(Modules) ->
|
clientinfo() -> clientinfo(#{}).
|
||||||
[mock(Module) || Module <- Modules],
|
clientinfo(Init) ->
|
||||||
ok.
|
maps:merge(#{clientid => <<"clientid">>,
|
||||||
|
username => <<"username">>
|
||||||
|
}, Init).
|
||||||
|
|
||||||
unload(Modules) ->
|
subopts() -> subopts(#{}).
|
||||||
lists:foreach(fun(Module) ->
|
subopts(Init) ->
|
||||||
ok = meck:unload(Module)
|
maps:merge(?DEFAULT_SUBOPTS, Init).
|
||||||
end, Modules).
|
|
||||||
|
|
||||||
mock(Module) ->
|
delivery(QoS, Topic) ->
|
||||||
ok = meck:new(Module, [passthrough, no_history]),
|
{deliver, Topic, emqx_message:make(test, QoS, Topic, <<"payload">>)}.
|
||||||
do_mock(Module).
|
|
||||||
|
|
||||||
do_mock(emqx_metrics) ->
|
ts() -> erlang:system_time(second).
|
||||||
meck:expect(emqx_metrics, inc, fun(_Anything) -> ok end);
|
|
||||||
do_mock(emqx_broker) ->
|
|
||||||
meck:expect(emqx_broker, subscribe, fun(_, _, _) -> ok end),
|
|
||||||
meck:expect(emqx_broker, set_subopts, fun(_, _) -> ok end),
|
|
||||||
meck:expect(emqx_broker, unsubscribe, fun(_) -> ok end),
|
|
||||||
meck:expect(emqx_broker, publish, fun(_) -> ok end);
|
|
||||||
do_mock(emqx_misc) ->
|
|
||||||
meck:expect(emqx_misc, start_timer, fun(_, _) -> tref end);
|
|
||||||
do_mock(emqx_message) ->
|
|
||||||
meck:expect(emqx_message, set_header, fun(_Hdr, _Val, Msg) -> Msg end),
|
|
||||||
meck:expect(emqx_message, is_expired, fun(_Msg) -> (rand:uniform(16) > 8) end);
|
|
||||||
do_mock(emqx_hooks) ->
|
|
||||||
meck:expect(emqx_hooks, run, fun(_Hook, _Args) -> ok end);
|
|
||||||
do_mock(emqx_zone) ->
|
|
||||||
meck:expect(emqx_zone, get_env, fun(Env, Key, Default) -> maps:get(Key, Env, Default) end).
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,21 @@ init_per_suite(Config) ->
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
emqx_ct_helpers:stop_apps([]).
|
emqx_ct_helpers:stop_apps([]).
|
||||||
|
|
||||||
|
% t_is_ack_required(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_maybe_nack_dropped(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_nack_no_connection(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_maybe_ack(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_subscribers(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
t_random_basic(_) ->
|
t_random_basic(_) ->
|
||||||
ok = ensure_config(random),
|
ok = ensure_config(random),
|
||||||
ClientId = <<"ClientId">>,
|
ClientId = <<"ClientId">>,
|
||||||
|
|
@ -224,6 +239,15 @@ last_message(ExpectedPayload, Pids) ->
|
||||||
<<"not yet?">>
|
<<"not yet?">>
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
% t_dispatch(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_unsubscribe(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_subscribe(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% help functions
|
%% help functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,20 @@
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
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_get_error_state(_) ->
|
||||||
|
Conns = emqx_stats:getstats(),
|
||||||
|
?assertEqual([], Conns).
|
||||||
|
|
||||||
t_get_state(_) ->
|
t_get_state(_) ->
|
||||||
with_proc(fun() ->
|
with_proc(fun() ->
|
||||||
|
?assertEqual(undefined, emqx_stats:getstat('notExist')),
|
||||||
SetConnsCount = emqx_stats:statsfun('connections.count'),
|
SetConnsCount = emqx_stats:statsfun('connections.count'),
|
||||||
SetConnsCount(1),
|
SetConnsCount(1),
|
||||||
?assertEqual(1, emqx_stats:getstat('connections.count')),
|
?assertEqual(1, emqx_stats:getstat('connections.count')),
|
||||||
|
|
@ -56,6 +68,8 @@ t_update_interval(_) ->
|
||||||
UpdFun = fun() -> emqx_stats:setstat('connections.count', 1) end,
|
UpdFun = fun() -> emqx_stats:setstat('connections.count', 1) end,
|
||||||
ok = emqx_stats:update_interval(stats_test, UpdFun),
|
ok = emqx_stats:update_interval(stats_test, UpdFun),
|
||||||
timer:sleep(SleepMs),
|
timer:sleep(SleepMs),
|
||||||
|
ok = emqx_stats:update_interval(stats_test, UpdFun),
|
||||||
|
timer:sleep(SleepMs),
|
||||||
?assertEqual(1, emqx_stats:getstat('connections.count'))
|
?assertEqual(1, emqx_stats:getstat('connections.count'))
|
||||||
end, TickMs).
|
end, TickMs).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,27 @@ end_per_suite(_Config) ->
|
||||||
ok = emqx_logger:set_log_level(error),
|
ok = emqx_logger:set_log_level(error),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
% t_version(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_sysdescr(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_uptime(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_datetime(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_sys_interval(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_sys_heatbeat_interval(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
|
% t_info(_) ->
|
||||||
|
% error('TODO').
|
||||||
|
|
||||||
t_prop_sys(_) ->
|
t_prop_sys(_) ->
|
||||||
Opts = [{numtests, 100}, {to_file, user}],
|
Opts = [{numtests, 100}, {to_file, user}],
|
||||||
ok = load(?mock_modules),
|
ok = load(?mock_modules),
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@
|
||||||
concat_str("long_gc warning: pid = ~p, info: ~p", self(), "hello"), "hello"},
|
concat_str("long_gc warning: pid = ~p, info: ~p", self(), "hello"), "hello"},
|
||||||
{self(), long_schedule,
|
{self(), long_schedule,
|
||||||
concat_str("long_schedule warning: pid = ~p, info: ~p", self(), "hello"), "hello"},
|
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,
|
{self(), busy_port,
|
||||||
concat_str("busy_port warning: suspid = ~p, port = ~p",
|
concat_str("busy_port warning: suspid = ~p, port = ~p",
|
||||||
self(), list_to_port("#Port<0.4>")), list_to_port("#Port<0.4>")},
|
self(), list_to_port("#Port<0.4>")), list_to_port("#Port<0.4>")},
|
||||||
|
|
@ -41,12 +43,35 @@
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
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: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.
|
Config.
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
end_per_testcase(_, _Config) ->
|
||||||
emqx_ct_helpers:stop_apps([]).
|
emqx_ct_helpers:stop_apps([]).
|
||||||
|
|
||||||
t_sys_mon(_Config) ->
|
t_sys_mon(_Config) ->
|
||||||
|
|
@ -55,6 +80,13 @@ t_sys_mon(_Config) ->
|
||||||
validate_sys_mon_info(PidOrPort, SysMonName,ValidateInfo, InfoOrPort)
|
validate_sys_mon_info(PidOrPort, SysMonName,ValidateInfo, InfoOrPort)
|
||||||
end, ?INPUTINFO).
|
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) ->
|
validate_sys_mon_info(PidOrPort, SysMonName,ValidateInfo, InfoOrPort) ->
|
||||||
{ok, C} = emqtt:start_link([{host, "localhost"}]),
|
{ok, C} = emqtt:start_link([{host, "localhost"}]),
|
||||||
{ok, _} = emqtt:connect(C),
|
{ok, _} = emqtt:connect(C),
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,3 @@ t_now_secs(_) ->
|
||||||
|
|
||||||
t_now_ms(_) ->
|
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())).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -229,4 +229,3 @@ bench(Case, Fun, Args) ->
|
||||||
]),
|
]),
|
||||||
ct:pal("Time consumed by ~s: ~.3f(us)~nCall ~s per second: ~w",
|
ct:pal("Time consumed by ~s: ~.3f(us)~nCall ~s per second: ~w",
|
||||||
[Case, Time/?N, Case, (?N * 1000000) div Time]).
|
[Case, Time/?N, Case, (?N * 1000000) div Time]).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ init_per_suite(Config) ->
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
emqx_ct_helpers:stop_apps([]).
|
emqx_ct_helpers:stop_apps([]).
|
||||||
|
|
||||||
|
|
||||||
t_start_traces(_Config) ->
|
t_start_traces(_Config) ->
|
||||||
{ok, T} = emqtt:start_link([{host, "localhost"},
|
{ok, T} = emqtt:start_link([{host, "localhost"},
|
||||||
{clientid, <<"client">>},
|
{clientid, <<"client">>},
|
||||||
|
|
@ -46,9 +47,12 @@ t_start_traces(_Config) ->
|
||||||
{error, _} = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"),
|
{error, _} = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"),
|
||||||
emqx_logger:set_log_level(debug),
|
emqx_logger:set_log_level(debug),
|
||||||
ok = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"),
|
ok = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"),
|
||||||
ok = emqx_tracer:start_trace({clientid, <<"client2">>}, all, "tmp/client2.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, 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, <<"a/#">>}, all, "tmp/topic_trace.log"),
|
||||||
|
ok = emqx_tracer:start_trace({topic, <<"b/#">>}, all, "tmp/topic_trace.log"),
|
||||||
ct:sleep(100),
|
ct:sleep(100),
|
||||||
|
|
||||||
%% Verify the tracing file exits
|
%% Verify the tracing file exits
|
||||||
|
|
@ -59,7 +63,9 @@ t_start_traces(_Config) ->
|
||||||
%% Get current traces
|
%% Get current traces
|
||||||
?assertEqual([{{clientid,"client"},{debug,"tmp/client.log"}},
|
?assertEqual([{{clientid,"client"},{debug,"tmp/client.log"}},
|
||||||
{{clientid,"client2"},{debug,"tmp/client2.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
|
%% set the overall log level to debug
|
||||||
emqx_logger:set_log_level(debug),
|
emqx_logger:set_log_level(debug),
|
||||||
|
|
@ -76,7 +82,11 @@ t_start_traces(_Config) ->
|
||||||
%% Stop tracing
|
%% Stop tracing
|
||||||
ok = emqx_tracer:stop_trace({clientid, <<"client">>}),
|
ok = emqx_tracer:stop_trace({clientid, <<"client">>}),
|
||||||
ok = emqx_tracer:stop_trace({clientid, <<"client2">>}),
|
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, <<"a/#">>}),
|
||||||
|
ok = emqx_tracer:stop_trace({topic, <<"b/#">>}),
|
||||||
|
{error, _Reason} = emqx_tracer:stop_trace({topic, <<"c/#">>}),
|
||||||
emqtt:disconnect(T),
|
emqtt:disconnect(T),
|
||||||
|
|
||||||
emqx_logger:set_log_level(warning).
|
emqx_logger:set_log_level(warning).
|
||||||
|
|
||||||
|
|
@ -104,7 +104,8 @@ t_load(_Config) ->
|
||||||
|
|
||||||
t_systeminfo(_Config) ->
|
t_systeminfo(_Config) ->
|
||||||
Keys = [Key || {Key, _} <- emqx_vm:get_system_info()],
|
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) ->
|
t_mem_info(_Config) ->
|
||||||
application:ensure_all_started(os_mon),
|
application:ensure_all_started(os_mon),
|
||||||
|
|
@ -139,10 +140,19 @@ t_get_ets_info(_Config) ->
|
||||||
ets:new(test, [named_table]),
|
ets:new(test, [named_table]),
|
||||||
[] = emqx_vm:get_ets_info(test1),
|
[] = emqx_vm:get_ets_info(test1),
|
||||||
EtsInfo = emqx_vm:get_ets_info(test),
|
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) ->
|
t_get_ets_object(_Config) ->
|
||||||
ets:new(test, [named_table]),
|
ets:new(test, [named_table]),
|
||||||
|
[] = emqx_vm:get_ets_object(test),
|
||||||
ets:insert(test, {k, v}),
|
ets:insert(test, {k, v}),
|
||||||
[{k, v}] = emqx_vm:get_ets_object(test).
|
[{k, v}] = emqx_vm:get_ets_object(test).
|
||||||
|
|
||||||
|
|
@ -150,7 +160,20 @@ t_get_port_types(_Config) ->
|
||||||
emqx_vm:get_port_types().
|
emqx_vm:get_port_types().
|
||||||
|
|
||||||
t_get_port_info(_Config) ->
|
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) ->
|
t_scheduler_usage(_Config) ->
|
||||||
emqx_vm:scheduler_usage(5000).
|
emqx_vm:scheduler_usage(5000).
|
||||||
|
|
@ -170,3 +193,21 @@ t_get_process_group_leader_info(_Config) ->
|
||||||
t_get_process_limit(_Config) ->
|
t_get_process_limit(_Config) ->
|
||||||
emqx_vm:get_process_limit().
|
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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,13 @@ t_api(_) ->
|
||||||
end),
|
end),
|
||||||
gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}),
|
gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}),
|
||||||
{ok, _} = emqx_vm_mon:start_link([{check_interval, 1},
|
{ok, _} = emqx_vm_mon:start_link([{check_interval, 1},
|
||||||
{process_high_watermark, 0},
|
{process_high_watermark, 0.8},
|
||||||
{process_low_watermark, 0.6}]),
|
{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),
|
?WAIT({Ref, set_alarm, {too_many_processes, _Count}}, 2000),
|
||||||
?assertEqual(true, lists:keymember(too_many_processes, 1, alarm_handler:get_alarms())),
|
?assertEqual(true, lists:keymember(too_many_processes, 1, alarm_handler:get_alarms())),
|
||||||
emqx_vm_mon:set_process_high_watermark(0.8),
|
emqx_vm_mon:set_process_high_watermark(0.8),
|
||||||
|
|
@ -69,7 +74,10 @@ t_api(_) ->
|
||||||
?WAIT({Ref, clear_alarm, too_many_processes}, 3000),
|
?WAIT({Ref, clear_alarm, too_many_processes}, 3000),
|
||||||
?assertEqual(false, lists:keymember(too_many_processes, 1, alarm_handler:get_alarms())),
|
?assertEqual(false, lists:keymember(too_many_processes, 1, alarm_handler:get_alarms())),
|
||||||
emqx_vm_mon:set_check_interval(20),
|
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
|
after
|
||||||
meck:unload(alarm_handler)
|
meck:unload(alarm_handler)
|
||||||
end.
|
end.
|
||||||
|
|
|
||||||
|
|
@ -16,42 +16,206 @@
|
||||||
|
|
||||||
-module(emqx_ws_connection_SUITE).
|
-module(emqx_ws_connection_SUITE).
|
||||||
|
|
||||||
|
-include("emqx_mqtt.hrl").
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
-compile(export_all).
|
-compile(export_all).
|
||||||
-compile(nowarn_export_all).
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-import(emqx_ws_connection,
|
||||||
|
[ websocket_handle/2
|
||||||
|
, websocket_info/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
-define(STATS_KEYS, [recv_oct, recv_cnt, send_oct, send_cnt,
|
||||||
|
recv_pkt, recv_msg, send_pkt, send_msg
|
||||||
|
]).
|
||||||
|
|
||||||
all() -> emqx_ct:all(?MODULE).
|
all() -> emqx_ct:all(?MODULE).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% CT callbacks
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
init_per_suite(Config) ->
|
init_per_suite(Config) ->
|
||||||
emqx_ct_helpers:boot_modules(all),
|
|
||||||
emqx_ct_helpers:start_apps([]),
|
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
emqx_ct_helpers:stop_apps([]).
|
ok.
|
||||||
|
|
||||||
t_basic(_) ->
|
init_per_testcase(_TestCase, Config) ->
|
||||||
Topic = <<"TopicA">>,
|
%% Meck CowboyReq
|
||||||
{ok, C} = emqtt:start_link([{host, "127.0.0.1"}, {port, 8083}]),
|
ok = meck:new(cowboy_req, [passthrough, no_history]),
|
||||||
{ok, _} = emqtt:ws_connect(C),
|
ok = meck:expect(cowboy_req, peer, fun(_) -> {{127,0,0,1}, 3456} end),
|
||||||
{ok, _, [1]} = emqtt:subscribe(C, Topic, qos1),
|
ok = meck:expect(cowboy_req, sock, fun(_) -> {{127,0,0,1}, 8883} end),
|
||||||
{ok, _, [2]} = emqtt:subscribe(C, Topic, qos2),
|
ok = meck:expect(cowboy_req, cert, fun(_) -> undefined end),
|
||||||
{ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2),
|
ok = meck:expect(cowboy_req, parse_cookies, fun(_) -> undefined end),
|
||||||
{ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2),
|
%% Meck Channel
|
||||||
{ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2),
|
ok = meck:new(emqx_channel, [passthrough, no_history]),
|
||||||
?assertEqual(3, length(recv_msgs(3))),
|
%% Meck Metrics
|
||||||
ok = emqtt:disconnect(C).
|
ok = meck:new(emqx_metrics, [passthrough, no_history]),
|
||||||
|
ok = meck:expect(emqx_metrics, inc, fun(_, _) -> ok end),
|
||||||
|
ok = meck:expect(emqx_metrics, inc_recv, fun(_) -> ok end),
|
||||||
|
ok = meck:expect(emqx_metrics, inc_sent, fun(_) -> ok end),
|
||||||
|
Config.
|
||||||
|
|
||||||
recv_msgs(Count) ->
|
end_per_testcase(_TestCase, Config) ->
|
||||||
recv_msgs(Count, []).
|
ok = meck:unload(cowboy_req),
|
||||||
|
ok = meck:unload(emqx_channel),
|
||||||
|
ok = meck:unload(emqx_metrics),
|
||||||
|
Config.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Test Cases
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
%%TODO:...
|
||||||
|
t_ws_conn_init(_) ->
|
||||||
|
with_ws_conn(fun(_WsConn) -> ok end).
|
||||||
|
|
||||||
|
t_ws_conn_info(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) ->
|
||||||
|
#{sockinfo := SockInfo} = emqx_ws_connection:info(WsConn),
|
||||||
|
#{socktype := ws,
|
||||||
|
peername := {{127,0,0,1}, 3456},
|
||||||
|
sockname := {{127,0,0,1}, 8883},
|
||||||
|
sockstate := running} = SockInfo
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_websocket_init(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) ->
|
||||||
|
#{sockinfo := SockInfo} = emqx_ws_connection:info(WsConn),
|
||||||
|
#{socktype := ws,
|
||||||
|
peername := {{127,0,0,1}, 3456},
|
||||||
|
sockname := {{127,0,0,1}, 8883},
|
||||||
|
sockstate := running
|
||||||
|
} = SockInfo
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_websocket_handle_binary(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) ->
|
||||||
|
{ok, _} = websocket_handle({binary, [<<>>]}, WsConn)
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_websocket_handle_ping_pong(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) ->
|
||||||
|
{ok, WsConn} = websocket_handle(ping, WsConn),
|
||||||
|
{ok, WsConn} = websocket_handle(pong, WsConn),
|
||||||
|
{ok, WsConn} = websocket_handle({ping, <<>>}, WsConn),
|
||||||
|
{ok, WsConn} = websocket_handle({pong, <<>>}, WsConn)
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_websocket_handle_bad_frame(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) ->
|
||||||
|
{stop, WsConn1} = websocket_handle({badframe, <<>>}, WsConn),
|
||||||
|
?assertEqual({shutdown, unexpected_ws_frame}, stop_reason(WsConn1))
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_websocket_info_call(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) ->
|
||||||
|
From = {make_ref(), self()},
|
||||||
|
Call = {call, From, badreq},
|
||||||
|
websocket_info(Call, WsConn)
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_websocket_info_cast(_) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_info, fun(_Msg, Channel) -> {ok, Channel} end),
|
||||||
|
with_ws_conn(fun(WsConn) -> websocket_info({cast, msg}, WsConn) end).
|
||||||
|
|
||||||
|
t_websocket_info_incoming(_) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_in, fun(_Packet, Channel) -> {ok, Channel} end),
|
||||||
|
with_ws_conn(fun(WsConn) ->
|
||||||
|
Connect = ?CONNECT_PACKET(
|
||||||
|
#mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5,
|
||||||
|
proto_name = <<"MQTT">>,
|
||||||
|
clientid = <<>>,
|
||||||
|
clean_start = true,
|
||||||
|
keepalive = 60}),
|
||||||
|
{ok, WsConn1} = websocket_info({incoming, Connect}, WsConn),
|
||||||
|
Publish = ?PUBLISH_PACKET(?QOS_1, <<"t">>, 1, <<"payload">>),
|
||||||
|
{ok, _WsConn2} = websocket_info({incoming, Publish}, WsConn1)
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_websocket_info_deliver(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_out,
|
||||||
|
fun(Delivers, Channel) ->
|
||||||
|
Packets = [emqx_message:to_packet(1, Msg) || {deliver, _, Msg} <- Delivers],
|
||||||
|
{ok, {outgoing, Packets}, Channel}
|
||||||
|
end),
|
||||||
|
Deliver = {deliver, <<"#">>, emqx_message:make(<<"topic">>, <<"payload">>)},
|
||||||
|
{reply, {binary, _Data}, _WsConn1} = websocket_info(Deliver, WsConn)
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_websocket_info_timeout(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) ->
|
||||||
|
websocket_info({timeout, make_ref(), keepalive}, WsConn),
|
||||||
|
websocket_info({timeout, make_ref(), emit_stats}, WsConn),
|
||||||
|
websocket_info({timeout, make_ref(), retry_delivery}, WsConn)
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_websocket_info_close(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) ->
|
||||||
|
{stop, WsConn1} = websocket_info({close, sock_error}, WsConn),
|
||||||
|
?assertEqual({shutdown, sock_error}, stop_reason(WsConn1))
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_websocket_info_shutdown(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) ->
|
||||||
|
{stop, WsConn1} = websocket_info({shutdown, reason}, WsConn),
|
||||||
|
?assertEqual({shutdown, reason}, stop_reason(WsConn1))
|
||||||
|
end).
|
||||||
|
|
||||||
|
|
||||||
|
t_websocket_info_stop(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) ->
|
||||||
|
{stop, WsConn1} = websocket_info({stop, normal}, WsConn),
|
||||||
|
?assertEqual(normal, stop_reason(WsConn1))
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_websocket_close(_) ->
|
||||||
|
ok = meck:expect(emqx_channel, handle_info,
|
||||||
|
fun({sock_closed, badframe}, Channel) ->
|
||||||
|
{shutdown, sock_closed, Channel}
|
||||||
|
end),
|
||||||
|
with_ws_conn(fun(WsConn) ->
|
||||||
|
{stop, WsConn1} = emqx_ws_connection:websocket_close(badframe, WsConn),
|
||||||
|
?assertEqual(sock_closed, stop_reason(WsConn1))
|
||||||
|
end).
|
||||||
|
|
||||||
|
t_handle_call(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) -> ok end).
|
||||||
|
|
||||||
|
t_handle_info(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) -> ok end).
|
||||||
|
|
||||||
|
t_handle_timeout(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) -> ok end).
|
||||||
|
|
||||||
|
t_parse_incoming(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) -> ok end).
|
||||||
|
|
||||||
|
t_handle_incoming(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) -> ok end).
|
||||||
|
|
||||||
|
t_handle_return(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) -> ok end).
|
||||||
|
|
||||||
|
t_handle_outgoing(_) ->
|
||||||
|
with_ws_conn(fun(WsConn) -> ok end).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Helper functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
with_ws_conn(TestFun) ->
|
||||||
|
with_ws_conn(TestFun, []).
|
||||||
|
|
||||||
|
with_ws_conn(TestFun, Opts) ->
|
||||||
|
{ok, WsConn, _} = emqx_ws_connection:websocket_init(
|
||||||
|
[req, emqx_misc:merge_opts([{zone, external}], Opts)]),
|
||||||
|
TestFun(WsConn).
|
||||||
|
|
||||||
|
stop_reason(WsConn) ->
|
||||||
|
emqx_ws_connection:info(stop_reason, WsConn).
|
||||||
|
|
||||||
recv_msgs(0, Msgs) ->
|
|
||||||
Msgs;
|
|
||||||
recv_msgs(Count, Msgs) ->
|
|
||||||
receive
|
|
||||||
{publish, Msg} ->
|
|
||||||
recv_msgs(Count-1, [Msg|Msgs])
|
|
||||||
after 100 ->
|
|
||||||
Msgs
|
|
||||||
end.
|
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ init_per_suite(Config) ->
|
||||||
Config.
|
Config.
|
||||||
|
|
||||||
end_per_suite(_Config) ->
|
end_per_suite(_Config) ->
|
||||||
|
emqx_zone:unset_all_env(),
|
||||||
application:unset_env(emqx, zone_env),
|
application:unset_env(emqx, zone_env),
|
||||||
application:unset_env(emqx, zones).
|
application:unset_env(emqx, zones).
|
||||||
|
|
||||||
|
|
@ -99,3 +100,5 @@ t_uncovered_func(_) ->
|
||||||
ok = Pid ! ok,
|
ok = Pid ! ok,
|
||||||
emqx_zone:stop().
|
emqx_zone:stop().
|
||||||
|
|
||||||
|
t_frame_options(_) ->
|
||||||
|
?assertMatch(#{strict_mode := _, max_size := _ }, emqx_zone:mqtt_frame_options(zone)).
|
||||||
Loading…
Reference in New Issue