Quellcode durchsuchen

Remove bridge functions from emqx (#2652)

* Remove bridge functions from emqx
Gilbert vor 6 Jahren
Ursprung
Commit
9df6345a6c

+ 0 - 328
etc/emqx.conf

@@ -1806,334 +1806,6 @@ listener.wss.external.send_timeout_close = on
 ## Value: Number
 ## listener.wss.external.max_frame_size = 0
 
-##--------------------------------------------------------------------
-## Bridges
-##--------------------------------------------------------------------
-
-##--------------------------------------------------------------------
-## Bridges to aws
-##--------------------------------------------------------------------
-
-## Bridge address: node name for local bridge, host:port for remote.
-##
-## Value: String
-## Example: emqx@127.0.0.1,  127.0.0.1:1883
-## bridge.aws.address = 127.0.0.1:1883
-
-## Protocol version of the bridge.
-##
-## Value: Enum
-## - mqttv5
-## - mqttv4
-## - mqttv3
-## bridge.aws.proto_ver = mqttv4
-
-## The ClientId of a remote bridge.
-##
-## Value: String
-## bridge.aws.client_id = bridge_aws
-
-## The Clean start flag of a remote bridge.
-##
-## Value: boolean
-## Default: true
-##
-## NOTE: Some IoT platforms require clean_start
-##       must be set to 'true'
-## bridge.aws.clean_start = true
-
-## The username for a remote bridge.
-##
-## Value: String
-## bridge.aws.username = user
-
-## The password for a remote bridge.
-##
-## Value: String
-## bridge.aws.password = passwd
-
-## Mountpoint of the bridge.
-##
-## Value: String
-## bridge.aws.mountpoint = bridge/aws/${node}/
-
-## Forward message topics
-##
-## Value: String
-## Example: topic1/#,topic2/#
-## bridge.aws.forwards = topic1/#,topic2/#
-
-## Bribge to remote server via SSL.
-##
-## Value: on | off
-## bridge.aws.ssl = off
-
-## PEM-encoded CA certificates of the bridge.
-##
-## Value: File
-## bridge.aws.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem
-
-## Client SSL Certfile of the bridge.
-##
-## Value: File
-## bridge.aws.certfile = {{ platform_etc_dir }}/certs/client-cert.pem
-
-## Client SSL Keyfile of the bridge.
-##
-## Value: File
-## bridge.aws.keyfile = {{ platform_etc_dir }}/certs/client-key.pem
-
-## SSL Ciphers used by the bridge.
-##
-## Value: String
-## bridge.aws.ciphers = ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384
-
-## Ciphers for TLS PSK.
-## Note that 'listener.ssl.external.ciphers' and 'listener.ssl.external.psk_ciphers' cannot
-## be configured at the same time.
-## See 'https://tools.ietf.org/html/rfc4279#section-2'.
-## bridge.aws.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA
-
-## Ping interval of a down bridge.
-##
-## Value: Duration
-## Default: 10 seconds
-## bridge.aws.keepalive = 60s
-
-## TLS versions used by the bridge.
-##
-## Value: String
-## bridge.aws.tls_versions = tlsv1.2,tlsv1.1,tlsv1
-
-## Subscriptions of the bridge topic.
-##
-## Value: String
-## bridge.aws.subscription.1.topic = cmd/topic1
-
-## Subscriptions of the bridge qos.
-##
-## Value: Number
-## bridge.aws.subscription.1.qos = 1
-
-## Subscriptions of the bridge topic.
-##
-## Value: String
-## bridge.aws.subscription.2.topic = cmd/topic2
-
-## Subscriptions of the bridge qos.
-##
-## Value: Number
-## bridge.aws.subscription.2.qos = 1
-
-## Start type of the bridge.
-##
-## Value: enum
-## manual
-## auto
-## bridge.aws.start_type = manual
-
-## Bridge reconnect time.
-##
-## Value: Duration
-## Default: 30 seconds
-## bridge.aws.reconnect_interval = 30s
-
-## Retry interval for bridge QoS1 message delivering.
-##
-## Value: Duration
-## bridge.aws.retry_interval = 20s
-
-## Inflight size.
-##
-## Value: Integer
-## bridge.aws.max_inflight_batches = 32
-
-## Max number of messages to collect in a batch for
-## each send call towards emqx_bridge_connect
-##
-## Value: Integer
-## default: 32
-## bridge.aws.queue.batch_count_limit = 32
-
-## Max number of bytes to collect in a batch for each
-## send call towards emqx_bridge_connect
-##
-## Value: Bytesize
-## default: 1000M
-## bridge.aws.queue.batch_bytes_limit = 1000MB
-
-## Base directory for replayq to store messages on disk
-## If this config entry is missing or set to undefined,
-## replayq works in a mem-only manner.
-##
-## Value: String
-## bridge.aws.queue.replayq_dir = {{ platform_data_dir }}/emqx_aws_bridge/
-
-## Replayq segment size
-##
-## Value: Bytesize
-## bridge.aws.queue.replayq_seg_bytes = 10MB
-
-##--------------------------------------------------------------------
-## Bridges to azure
-##--------------------------------------------------------------------
-
-## Bridge address: node name for local bridge, host:port for remote.
-##
-## Value: String
-## Example: emqx@127.0.0.1,  127.0.0.1:1883
-## bridge.azure.address = 127.0.0.1:1883
-
-## Protocol version of the bridge.
-##
-## Value: Enum
-## - mqttv5
-## - mqttv4
-## - mqttv3
-## bridge.azure.proto_ver = mqttv4
-
-## The ClientId of a remote bridge.
-##
-## Value: String
-## bridge.azure.client_id = bridge_azure
-
-## The Clean start flag of a remote bridge.
-##
-## Value: boolean
-## Default: true
-##
-## NOTE: Some IoT platforms require clean_start
-##       must be set to 'true'
-## bridge.azure.clean_start = true
-
-## The username for a remote bridge.
-##
-## Value: String
-## bridge.azure.username = user
-
-## The password for a remote bridge.
-##
-## Value: String
-## bridge.azure.password = passwd
-
-## Mountpoint of the bridge.
-##
-## Value: String
-## bridge.azure.mountpoint = bridge/aws/${node}/
-
-## Forward message topics
-##
-## Value: String
-## Example: topic1/#,topic2/#
-## bridge.azure.forwards = topic1/#,topic2/#
-
-## Bribge to remote server via SSL.
-##
-## Value: on | off
-## bridge.azure.ssl = off
-
-## PEM-encoded CA certificates of the bridge.
-##
-## Value: File
-## bridge.azure.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem
-
-## Client SSL Certfile of the bridge.
-##
-## Value: File
-## bridge.azure.certfile = {{ platform_etc_dir }}/certs/client-cert.pem
-
-## Client SSL Keyfile of the bridge.
-##
-## Value: File
-## bridge.azure.keyfile = {{ platform_etc_dir }}/certs/client-key.pem
-
-## SSL Ciphers used by the bridge.
-##
-## Value: String
-## bridge.azure.ciphers = ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384
-
-## Ciphers for TLS PSK.
-## Note that 'bridge.*.ciphers' and 'bridge.*.psk_ciphers' cannot
-## be configured at the same time.
-## See 'https://tools.ietf.org/html/rfc4279#section-2'.
-#bridge.azure.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA
-
-## Ping interval of a down bridge.
-##
-## Value: Duration
-## Default: 10 seconds
-## bridge.azure.keepalive = 60s
-
-## TLS versions used by the bridge.
-##
-## Value: String
-## bridge.azure.tls_versions = tlsv1.2,tlsv1.1,tlsv1
-
-## Subscriptions of the bridge topic.
-##
-## Value: String
-## bridge.azure.subscription.1.topic = cmd/topic1
-
-## Subscriptions of the bridge qos.
-##
-## Value: Number
-## bridge.azure.subscription.1.qos = 1
-
-## Subscriptions of the bridge topic.
-##
-## Value: String
-## bridge.azure.subscription.2.topic = cmd/topic2
-
-## Subscriptions of the bridge qos.
-##
-## Value: Number
-## bridge.azure.subscription.2.qos = 1
-
-## Start type of the bridge.
-##
-## Value: enum
-## manual
-## auto
-## bridge.azure.start_type = manual
-
-## Bridge reconnect time.
-##
-## Value: Duration
-## Default: 30 seconds
-## bridge.azure.reconnect_interval = 30s
-
-## Retry interval for bridge QoS1 message delivering.
-##
-## Value: Duration
-## bridge.azure.retry_interval = 20s
-
-## Inflight size.
-##
-## Value: Integer
-## bridge.azure.max_inflight_batches = 32
-
-## Maximum number of messages in one batch when sending to remote borkers
-## NOTE: when bridging via MQTT connection to remote broker, this config is only
-##       used for internal message passing optimization as the underlying MQTT
-##       protocol does not supports batching.
-##
-## Value: Integer
-## default: 32
-## bridge.azure.queue.batch_size = 32
-
-## Base directory for replayq to store messages on disk
-## If this config entry is missing or set to undefined,
-## replayq works in a mem-only manner.
-##
-## Value: String
-## bridge.azure.queue.replayq_dir = {{ platform_data_dir }}/emqx_aws_bridge/
-
-## Replayq segment size
-##
-## Value: Bytesize
-## bridge.azure.queue.replayq_seg_bytes = 10MB
-
-
 ##--------------------------------------------------------------------
 ## Modules
 ##--------------------------------------------------------------------

+ 0 - 231
priv/emqx.schema

@@ -1712,237 +1712,6 @@ end}.
                                                ++ cuttlefish_variable:filter_by_prefix("listener.wss", Conf)])
 end}.
 
-%%--------------------------------------------------------------------
-%% Bridges
-%%--------------------------------------------------------------------
-{mapping, "bridge.$name.address", "emqx.bridges", [
-  {datatype, string}
-]}.
-
-{mapping, "bridge.$name.proto_ver", "emqx.bridges", [
-  {datatype, {enum, [mqttv3, mqttv4, mqttv5]}}
-]}.
-
-{mapping, "bridge.$name.client_id", "emqx.bridges", [
-  {datatype, string}
-]}.
-
-{mapping, "bridge.$name.clean_start", "emqx.bridges", [
-  {default, true},
-  {datatype, {enum, [true, false]}}
-]}.
-
-{mapping, "bridge.$name.username", "emqx.bridges", [
-  {datatype, string}
-]}.
-
-{mapping, "bridge.$name.password", "emqx.bridges", [
-  {datatype, string}
-]}.
-
-{mapping, "bridge.$name.mountpoint", "emqx.bridges", [
-  {datatype, string}
-]}.
-
-{mapping, "bridge.$name.forwards", "emqx.bridges", [
-  {datatype, string},
-  {default, ""}
-]}.
-
-{mapping, "bridge.$name.ssl", "emqx.bridges", [
-  {datatype, flag},
-  {default, off}
-]}.
-
-{mapping, "bridge.$name.cacertfile", "emqx.bridges", [
-  {datatype, string}
-]}.
-
-{mapping, "bridge.$name.certfile", "emqx.bridges", [
-  {datatype, string}
-]}.
-
-{mapping, "bridge.$name.keyfile", "emqx.bridges", [
-  {datatype, string}
-]}.
-
-{mapping, "bridge.$name.ciphers", "emqx.bridges", [
-  {datatype, string}
-]}.
-
-{mapping, "bridge.$name.psk_ciphers", "emqx.bridges", [
-  {datatype, string}
-]}.
-
-{mapping, "bridge.$name.keepalive", "emqx.bridges", [
-  {default, "10s"},
-  {datatype, {duration, ms}}
-]}.
-
-{mapping, "bridge.$name.tls_versions", "emqx.bridges", [
-  {datatype, string},
-  {default, "tlsv1,tlsv1.1,tlsv1.2"}
-]}.
-
-{mapping, "bridge.$name.subscription.$id.topic", "emqx.bridges", [
-  {datatype, string}
-]}.
-
-{mapping, "bridge.$name.subscription.$id.qos", "emqx.bridges", [
-  {datatype, integer}
-]}.
-
-{mapping, "bridge.$name.start_type", "emqx.bridges", [
-  {datatype, {enum, [manual, auto]}},
-  {default, auto}
-]}.
-
-{mapping, "bridge.$name.reconnect_interval", "emqx.bridges", [
-  {default, "30s"},
-  {datatype, {duration, ms}}
-]}.
-
-{mapping, "bridge.$name.retry_interval", "emqx.bridges", [
-  {default, "20s"},
-  {datatype, {duration, ms}}
-]}.
-
-{mapping, "bridge.$name.max_inflight_batches", "emqx.bridges", [
-  {default, 0},
-  {datatype, integer}
-]}.
-
-{mapping, "bridge.$name.queue.batch_count_limit", "emqx.bridges", [
-  {datatype, integer}
-]}.
-
-{mapping, "bridge.$name.queue.batch_bytes_limit", "emqx.bridges", [
-  {datatype, bytesize}
-]}.
-
-{mapping, "bridge.$name.queue.replayq_dir", "emqx.bridges", [
-  {datatype, string}
-]}.
-
-{mapping, "bridge.$name.queue.replayq_seg_bytes", "emqx.bridges", [
-  {datatype, bytesize}
-]}.
-
-{translation, "emqx.bridges", fun(Conf) ->
-    MapPSKCiphers = fun(PSKCiphers) ->
-                      lists:map(
-                          fun("PSK-AES128-CBC-SHA") -> {psk, aes_128_cbc, sha};
-                             ("PSK-AES256-CBC-SHA") -> {psk, aes_256_cbc, sha};
-                             ("PSK-3DES-EDE-CBC-SHA") -> {psk, '3des_ede_cbc', sha};
-                             ("PSK-RC4-SHA") -> {psk, rc4_128, sha}
-                          end, PSKCiphers)
-                    end,
-
-    Split = fun(undefined) -> undefined; (S) -> string:tokens(S, ",") end,
-
-    IsSsl = fun(cacertfile)   -> true;
-               (certfile)     -> true;
-               (keyfile)      -> true;
-               (ciphers)      -> true;
-               (psk_ciphers)  -> true;
-               (tls_versions) -> true;
-               (_Opt)         -> false
-            end,
-
-    Parse = fun(tls_versions, Vers) ->
-                    [{versions, [list_to_atom(S) || S <- Split(Vers)]}];
-               (ciphers, Ciphers) ->
-                    [{ciphers, Split(Ciphers)}];
-               (psk_ciphers, Ciphers) ->
-                    [{ciphers, MapPSKCiphers(Split(Ciphers))}, {user_lookup_fun, {fun emqx_psk:lookup/3, <<>>}}];
-               (Opt, Val) ->
-                    [{Opt, Val}]
-            end,
-
-    Merge = fun(forwards, Val, Opts) ->
-                  [{forwards, string:tokens(Val, ",")}|Opts];
-               (Opt, Val, Opts) ->
-                  case IsSsl(Opt) of
-                      true ->
-                          SslOpts = Parse(Opt, Val) ++ proplists:get_value(ssl_opts, Opts, []),
-                          lists:ukeymerge(1, [{ssl_opts, SslOpts}], lists:usort(Opts));
-                      false ->
-                          [{Opt, Val}|Opts]
-                  end
-            end,
-    Queue = fun(Name) ->
-                Configs = cuttlefish_variable:filter_by_prefix("bridge." ++ Name ++ ".queue", Conf),
-                QOpts = [{list_to_atom(QOpt), QValue}|| {[_, _, "queue", QOpt], QValue} <- Configs],
-                maps:from_list(QOpts)
-            end,
-    Subscriptions = fun(Name) ->
-                        Configs = cuttlefish_variable:filter_by_prefix("bridge." ++ Name ++ ".subscription", Conf),
-                        lists:zip([Topic || {_, Topic} <- lists:sort([{I, Topic} || {[_, _, "subscription", I, "topic"], Topic} <- Configs])],
-                                  [QoS || {_, QoS} <- lists:sort([{I, QoS} || {[_, _, "subscription", I, "qos"], QoS} <- Configs])])
-                    end,
-    IsNodeAddr = fun(Addr) ->
-                      case string:tokens(Addr, "@") of
-                          [_NodeName, _Hostname] -> true;
-                           _ -> false
-                      end
-                 end,
-    ConnMod = fun(Name) ->
-                      [AddrConfig] = cuttlefish_variable:filter_by_prefix("bridge." ++ Name ++ ".address", Conf),
-                      {_, Addr} = AddrConfig,
-                      Subs = Subscriptions(Name),
-                      case IsNodeAddr(Addr) of
-                          true when Subs =/= [] ->
-                              error({"subscriptions are not supported when bridging between emqx nodes", Name, Subs});
-                          true ->
-                              emqx_bridge_rpc;
-                          false ->
-                              emqx_bridge_mqtt
-                      end
-              end,
-    %% to be backward compatible
-    Translate =
-        fun Tr(queue, Q, Cfg) ->
-                NewQ = maps:fold(Tr, #{}, Q),
-                Cfg#{queue => NewQ};
-            Tr(address, Addr0, Cfg) ->
-                Addr = case IsNodeAddr(Addr0) of
-                           true -> list_to_atom(Addr0);
-                           false -> Addr0
-                       end,
-                Cfg#{address => Addr};
-            Tr(batch_size, Count, Cfg) ->
-                Cfg#{batch_count_limit => Count};
-            Tr(reconnect_interval, Ms, Cfg) ->
-                Cfg#{reconnect_delay_ms => Ms};
-            Tr(max_inflight, Count, Cfg) ->
-                Cfg#{max_inflight_batches => Count};
-            Tr(proto_ver, Ver, Cfg) ->
-                Cfg#{proto_ver =>
-                   case Ver of
-                       mqttv3 -> v3;
-                       mqttv4 -> v4;
-                       mqttv5 -> v5;
-                       _ -> v4
-                   end};
-            Tr(Key, Value, Cfg) ->
-                Cfg#{Key => Value}
-         end,
-    C = lists:foldl(
-        fun({["bridge", Name, Opt], Val}, Acc) ->
-                %% e.g #{aws => [{OptKey, OptVal}]}
-                Init = [{list_to_atom(Opt), Val},
-                        {connect_module, ConnMod(Name)},
-                        {subscriptions, Subscriptions(Name)},
-                        {queue, Queue(Name)}],
-                maps:update_with(list_to_atom(Name), fun(Opts) -> Merge(list_to_atom(Opt), Val, Opts) end, Init, Acc);
-           (_, Acc) -> Acc
-        end, #{}, lists:usort(cuttlefish_variable:filter_by_prefix("bridge.", Conf))),
-    C1 = maps:map(fun(Bn, Bc) ->
-                      maps:to_list(maps:fold(Translate, #{}, maps:from_list(Bc)))
-                  end, C),
-    maps:to_list(C1)
-end}.
-
 %%--------------------------------------------------------------------
 %% Modules
 %%--------------------------------------------------------------------

+ 0 - 610
src/emqx_bridge.erl

@@ -1,610 +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 Bridge works in two layers (1) batching layer (2) transport layer
-%% The `bridge' batching layer collects local messages in batches and sends over
-%% to remote MQTT node/cluster via `connetion' transport layer.
-%% In case `REMOTE' is also an EMQX node, `connection' is recommended to be
-%% the `gen_rpc' based implementation `emqx_bridge_rpc'. Otherwise `connection'
-%% has to be `emqx_bridge_mqtt'.
-%%
-%% ```
-%% +------+                        +--------+
-%% | EMQX |                        | REMOTE |
-%% |      |                        |        |
-%% |   (bridge) <==(connection)==> |        |
-%% |      |                        |        |
-%% |      |                        |        |
-%% +------+                        +--------+
-%% '''
-%%
-%%
-%% This module implements 2 kinds of APIs with regards to batching and
-%% messaging protocol. (1) A `gen_statem' based local batch collector;
-%% (2) APIs for incoming remote batches/messages.
-%%
-%% Batch collector state diagram
-%%
-%% [standing_by] --(0) --> [connecting] --(2)--> [connected]
-%%                          |        ^                 |
-%%                          |        |                 |
-%%                          '--(1)---'--------(3)------'
-%%
-%% (0): auto or manual start
-%% (1): retry timeout
-%% (2): successfuly connected to remote node/cluster
-%% (3): received {disconnected, conn_ref(), Reason} OR
-%%      failed to send to remote node/cluster.
-%%
-%% NOTE: A bridge worker may subscribe to multiple (including wildcard)
-%% local topics, and the underlying `emqx_bridge_connect' may subscribe to
-%% multiple remote topics, however, worker/connections are not designed
-%% to support automatic load-balancing, i.e. in case it can not keep up
-%% with the amount of messages comming in, administrator should split and
-%% balance topics between worker/connections manually.
-%%
-%% NOTES:
-%% * Local messages are all normalised to QoS-1 when exporting to remote
-
--module(emqx_bridge).
--behaviour(gen_statem).
-
-%% APIs
--export([ start_link/2
-        , import_batch/2
-        , handle_ack/2
-        , stop/1
-        ]).
-
-%% gen_statem callbacks
--export([ terminate/3
-        , code_change/4
-        , init/1
-        , callback_mode/0
-        ]).
-
-%% state functions
--export([ standing_by/3
-        , connecting/3
-        , connected/3
-        ]).
-
-%% management APIs
--export([ ensure_started/1
-        , ensure_started/2
-        , ensure_stopped/1
-        , ensure_stopped/2
-        , status/1
-        ]).
-
--export([ get_forwards/1
-        , ensure_forward_present/2
-        , ensure_forward_absent/2
-        ]).
-
--export([ get_subscriptions/1
-        , ensure_subscription_present/3
-        , ensure_subscription_absent/2
-        ]).
-
--export_type([ config/0
-             , batch/0
-             , ack_ref/0
-             ]).
-
--type id() :: atom() | string() | pid().
--type qos() :: emqx_mqtt_types:qos().
--type config() :: map().
--type batch() :: [emqx_bridge_msg:exp_msg()].
--type ack_ref() :: term().
--type topic() :: emqx_topic:topic().
-
--include("logger.hrl").
--include("emqx_mqtt.hrl").
-
--logger_header("[Bridge]").
-
-%% same as default in-flight limit for emqx_client
--define(DEFAULT_BATCH_COUNT, 32).
--define(DEFAULT_BATCH_BYTES, 1 bsl 20).
--define(DEFAULT_SEND_AHEAD, 8).
--define(DEFAULT_RECONNECT_DELAY_MS, timer:seconds(5)).
--define(DEFAULT_SEG_BYTES, (1 bsl 20)).
--define(NO_BRIDGE_HANDLER, undefined).
--define(NO_FROM, undefined).
--define(maybe_send, {next_event, internal, maybe_send}).
-
-%% @doc Start a bridge worker. Supported configs:
-%% start_type: 'manual' (default) or 'auto', when manual, bridge will stay
-%%      at 'standing_by' state until a manual call to start it.
-%% connect_module: The module which implements emqx_bridge_connect behaviour
-%%      and work as message batch transport layer
-%% reconnect_delay_ms: Delay in milli-seconds for the bridge worker to retry
-%%      in case of transportation failure.
-%% max_inflight_batches: Max number of batches allowed to send-ahead before
-%%      receiving confirmation from remote node/cluster
-%% mountpoint: The topic mount point for messages sent to remote node/cluster
-%%      `undefined', `<<>>' or `""' to disable
-%% forwards: Local topics to subscribe.
-%% queue.batch_bytes_limit: Max number of bytes to collect in a batch for each
-%%      send call towards emqx_bridge_connect
-%% queue.batch_count_limit: Max number of messages to collect in a batch for
-%%      each send call towards emqx_bridge_connect
-%% queue.replayq_dir: Directory where replayq should persist messages
-%% queue.replayq_seg_bytes: Size in bytes for each replayq segment file
-%%
-%% Find more connection specific configs in the callback modules
-%% of emqx_bridge_connect behaviour.
-start_link(Name, Config) when is_list(Config) ->
-    start_link(Name, maps:from_list(Config));
-start_link(Name, Config) ->
-    gen_statem:start_link({local, name(Name)}, ?MODULE, Config, []).
-
-%% @doc Manually start bridge worker. State idempotency ensured.
-ensure_started(Name) ->
-    gen_statem:call(name(Name), ensure_started).
-
-ensure_started(Name, Config) ->
-    case start_link(Name, Config) of
-        {ok, Pid} -> {ok, Pid};
-        {error, {already_started,Pid}} -> {ok, Pid}
-    end.
-
-%% @doc Manually stop bridge worker. State idempotency ensured.
-ensure_stopped(Id) ->
-    ensure_stopped(Id, 1000).
-
-ensure_stopped(Id, Timeout) ->
-    Pid = case id(Id) of
-              P when is_pid(P) -> P;
-              N -> whereis(N)
-          end,
-    case Pid of
-        undefined ->
-            ok;
-        _ ->
-            MRef = monitor(process, Pid),
-            unlink(Pid),
-            _ = gen_statem:call(id(Id), ensure_stopped, Timeout),
-            receive
-                {'DOWN', MRef, _, _, _} ->
-                    ok
-            after
-                Timeout ->
-                    exit(Pid, kill)
-            end
-    end.
-
-stop(Pid) -> gen_statem:stop(Pid).
-
-status(Pid) when is_pid(Pid) ->
-    gen_statem:call(Pid, status);
-status(Id) ->
-    status(name(Id)).
-
-%% @doc This function is to be evaluated on message/batch receiver side.
--spec import_batch(batch(), fun(() -> ok)) -> ok.
-import_batch(Batch, AckFun) ->
-    lists:foreach(fun emqx_broker:publish/1, emqx_bridge_msg:to_broker_msgs(Batch)),
-    AckFun().
-
-%% @doc This function is to be evaluated on message/batch exporter side
-%% when message/batch is accepted by remote node.
--spec handle_ack(pid(), ack_ref()) -> ok.
-handle_ack(Pid, Ref) when node() =:= node(Pid) ->
-    Pid ! {batch_ack, Ref},
-    ok.
-
-%% @doc Return all forwards (local subscriptions).
--spec get_forwards(id()) -> [topic()].
-get_forwards(Id) -> gen_statem:call(id(Id), get_forwards, timer:seconds(1000)).
-
-%% @doc Return all subscriptions (subscription over mqtt connection to remote broker).
--spec get_subscriptions(id()) -> [{emqx_topic:topic(), qos()}].
-get_subscriptions(Id) -> gen_statem:call(id(Id), get_subscriptions).
-
-%% @doc Add a new forward (local topic subscription).
--spec ensure_forward_present(id(), topic()) -> ok.
-ensure_forward_present(Id, Topic) ->
-    gen_statem:call(id(Id), {ensure_present, forwards, topic(Topic)}).
-
-%% @doc Ensure a forward topic is deleted.
--spec ensure_forward_absent(id(), topic()) -> ok.
-ensure_forward_absent(Id, Topic) ->
-    gen_statem:call(id(Id), {ensure_absent, forwards, topic(Topic)}).
-
-%% @doc Ensure subscribed to remote topic.
-%% NOTE: only applicable when connection module is emqx_bridge_mqtt
-%%       return `{error, no_remote_subscription_support}' otherwise.
--spec ensure_subscription_present(id(), topic(), qos()) -> ok | {error, any()}.
-ensure_subscription_present(Id, Topic, QoS) ->
-    gen_statem:call(id(Id), {ensure_present, subscriptions, {topic(Topic), QoS}}).
-
-%% @doc Ensure unsubscribed from remote topic.
-%% NOTE: only applicable when connection module is emqx_bridge_mqtt
--spec ensure_subscription_absent(id(), topic()) -> ok.
-ensure_subscription_absent(Id, Topic) ->
-    gen_statem:call(id(Id), {ensure_absent, subscriptions, topic(Topic)}).
-
-callback_mode() -> [state_functions, state_enter].
-
-%% @doc Config should be a map().
-init(Config) ->
-    erlang:process_flag(trap_exit, true),
-    Get = fun(K, D) -> maps:get(K, Config, D) end,
-    QCfg = maps:get(queue, Config, #{}),
-    GetQ = fun(K, D) -> maps:get(K, QCfg, D) end,
-    Dir = GetQ(replayq_dir, undefined),
-    QueueConfig =
-        case Dir =:= undefined orelse Dir =:= "" of
-            true -> #{mem_only => true};
-            false -> #{dir => Dir,
-                       seg_bytes => GetQ(replayq_seg_bytes, ?DEFAULT_SEG_BYTES)
-                      }
-        end,
-    Queue = replayq:open(QueueConfig#{sizer => fun emqx_bridge_msg:estimate_size/1,
-                                      marshaller => fun msg_marshaller/1}),
-    Topics = lists:sort([iolist_to_binary(T) || T <- Get(forwards, [])]),
-    Subs = lists:keysort(1, lists:map(fun({T0, QoS}) ->
-                                              T = iolist_to_binary(T0),
-                                              true = emqx_topic:validate({filter, T}),
-                                              {T, QoS}
-                                      end, Get(subscriptions, []))),
-    ConnectModule = maps:get(connect_module, Config),
-    ConnectConfig = maps:without([connect_module,
-                                  queue,
-                                  reconnect_delay_ms,
-                                  max_inflight_batches,
-                                  mountpoint,
-                                  forwards
-                                 ], Config#{subscriptions => Subs}),
-    ConnectFun = fun(SubsX) -> emqx_bridge_connect:start(ConnectModule, ConnectConfig#{subscriptions := SubsX}) end,
-    {ok, standing_by,
-     #{connect_module => ConnectModule,
-       connect_fun => ConnectFun,
-       start_type => Get(start_type, manual),
-       reconnect_delay_ms => maps:get(reconnect_delay_ms, Config, ?DEFAULT_RECONNECT_DELAY_MS),
-       batch_bytes_limit => GetQ(batch_bytes_limit, ?DEFAULT_BATCH_BYTES),
-       batch_count_limit => GetQ(batch_count_limit, ?DEFAULT_BATCH_COUNT),
-       max_inflight_batches => Get(max_inflight_batches, ?DEFAULT_SEND_AHEAD),
-       mountpoint => format_mountpoint(Get(mountpoint, undefined)),
-       forwards => Topics,
-       subscriptions => Subs,
-       replayq => Queue,
-       inflight => [],
-       connection => undefined,
-       bridge_handler => Get(bridge_handler, ?NO_BRIDGE_HANDLER)
-      }}.
-
-code_change(_Vsn, State, Data, _Extra) ->
-    {ok, State, Data}.
-
-terminate(_Reason, _StateName, #{replayq := Q} = State) ->
-    _ = disconnect(State),
-    _ = replayq:close(Q),
-    ok.
-
-%% @doc Standing by for manual start.
-standing_by(enter, _, #{start_type := auto}) ->
-    Action = {state_timeout, 0, do_connect},
-    {keep_state_and_data, Action};
-standing_by(enter, _, #{start_type := manual}) ->
-    keep_state_and_data;
-standing_by({call, From}, ensure_started, State) ->
-    do_connect({call, From}, standing_by, State);
-standing_by(state_timeout, do_connect, State) ->
-    {next_state, connecting, State};
-standing_by(info, Info, State) ->
-    ?LOG(info, "Bridge ~p discarded info event at state standing_by:\n~p", [name(), Info]),
-    {keep_state_and_data, State};
-standing_by(Type, Content, State) ->
-    common(standing_by, Type, Content, State).
-
-%% @doc Connecting state is a state with timeout.
-%% After each timeout, it re-enters this state and start a retry until
-%% successfuly connected to remote node/cluster.
-connecting(enter, connected, #{reconnect_delay_ms := Timeout}) ->
-    Action = {state_timeout, Timeout, reconnect},
-    {keep_state_and_data, Action};
-connecting(enter, _, State) ->
-    do_connect(enter, connecting, State);
-connecting(state_timeout, connected, State) ->
-    {next_state, connected, State};
-connecting(state_timeout, reconnect, _State) ->
-    repeat_state_and_data;
-connecting(info, {batch_ack, Ref}, State) ->
-    case do_ack(State, Ref) of
-        {ok, NewState} ->
-            {keep_state, NewState};
-        _ ->
-            keep_state_and_data
-    end;
-connecting(internal, maybe_send, _State) ->
-    keep_state_and_data;
-connecting(info, {disconnected, _Ref, _Reason}, _State) ->
-    keep_state_and_data;
-connecting(Type, Content, State) ->
-    common(connecting, Type, Content, State).
-
-%% @doc Send batches to remote node/cluster when in 'connected' state.
-connected(enter, _OldState, #{inflight := Inflight} = State) ->
-    case retry_inflight(State#{inflight := []}, Inflight) of
-        {ok, NewState} ->
-            Action = {state_timeout, 0, success},
-            {keep_state, NewState, Action};
-        {error, NewState} ->
-            Action = {state_timeout, 0, failure},
-            {keep_state, disconnect(NewState), Action}
-    end;
-connected(state_timeout, failure, State) ->
-    {next_state, connecting, State};
-connected(state_timeout, success, State) ->
-    {keep_state, State, ?maybe_send};
-connected(internal, maybe_send, State) ->
-    case pop_and_send(State) of
-        {ok, NewState} ->
-            {keep_state, NewState};
-        {error, NewState} ->
-            {next_state, connecting, disconnect(NewState)}
-    end;
-connected(info, {disconnected, ConnRef, Reason},
-          #{conn_ref := ConnRefCurrent} = State) ->
-    case ConnRefCurrent =:= ConnRef of
-        true ->
-            ?LOG(info, "Bridge ~p diconnected~nreason=~p", [name(), Reason]),
-            {next_state, connecting,
-             State#{conn_ref => undefined, connection => undefined}};
-        false ->
-            keep_state_and_data
-    end;
-connected(info, {batch_ack, Ref}, State) ->
-    case do_ack(State, Ref) of
-        stale ->
-            keep_state_and_data;
-        bad_order ->
-            %% try re-connect then re-send
-            ?LOG(error, "Bad order ack received by bridge ~p", [name()]),
-            {next_state, connecting, disconnect(State)};
-        {ok, NewState} ->
-            {keep_state, NewState, ?maybe_send}
-    end;
-connected(Type, Content, State) ->
-    common(connected, Type, Content, State).
-
-%% Common handlers
-common(StateName, {call, From}, status, _State) ->
-    {keep_state_and_data, [{reply, From, StateName}]};
-common(_StateName, {call, From}, ensure_started, _State) ->
-    {keep_state_and_data, [{reply, From, ok}]};
-common(_StateName, {call, From}, get_forwards, #{forwards := Forwards}) ->
-    {keep_state_and_data, [{reply, From, Forwards}]};
-common(_StateName, {call, From}, get_subscriptions, #{subscriptions := Subs}) ->
-    {keep_state_and_data, [{reply, From, Subs}]};
-common(_StateName, {call, From}, {ensure_present, What, Topic}, State) ->
-    {Result, NewState} = ensure_present(What, Topic, State),
-    {keep_state, NewState, [{reply, From, Result}]};
-common(_StateName, {call, From}, {ensure_absent, What, Topic}, State) ->
-    {Result, NewState} = ensure_absent(What, Topic, State),
-    {keep_state, NewState, [{reply, From, Result}]};
-common(_StateName, {call, From}, ensure_stopped, _State) ->
-    {stop_and_reply, {shutdown, manual},
-     [{reply, From, ok}]};
-common(_StateName, info, {dispatch, _, Msg},
-       #{replayq := Q} = State) ->
-    NewQ = replayq:append(Q, collect([Msg])),
-    {keep_state, State#{replayq => NewQ}, ?maybe_send};
-common(StateName, Type, Content, State) ->
-    ?LOG(notice, "Bridge ~p discarded ~p type event at state ~p:\n~p",
-          [name(), Type, StateName, Content]),
-    {keep_state, State}.
-
-eval_bridge_handler(State = #{bridge_handler := ?NO_BRIDGE_HANDLER}, _Msg) ->
-    State;
-eval_bridge_handler(State = #{bridge_handler := Handler}, Msg) ->
-    Handler(Msg),
-    State.
-
-ensure_present(Key, Topic, State) ->
-    Topics = maps:get(Key, State),
-    case is_topic_present(Topic, Topics) of
-        true ->
-            {ok, State};
-        false ->
-            R = do_ensure_present(Key, Topic, State),
-            {R, State#{Key := lists:usort([Topic | Topics])}}
-    end.
-
-ensure_absent(Key, Topic, State) ->
-    Topics = maps:get(Key, State),
-    case is_topic_present(Topic, Topics) of
-        true ->
-            R = do_ensure_absent(Key, Topic, State),
-            {R, State#{Key := ensure_topic_absent(Topic, Topics)}};
-        false ->
-            {ok, State}
-    end.
-
-ensure_topic_absent(_Topic, []) -> [];
-ensure_topic_absent(Topic, [{_, _} | _] = L) -> lists:keydelete(Topic, 1, L);
-ensure_topic_absent(Topic, L) -> lists:delete(Topic, L).
-
-is_topic_present({Topic, _QoS}, Topics) ->
-    is_topic_present(Topic, Topics);
-is_topic_present(Topic, Topics) ->
-    lists:member(Topic, Topics) orelse false =/= lists:keyfind(Topic, 1, Topics).
-
-do_connect(Type, StateName, #{ forwards := Forwards
-                             , subscriptions := Subs
-                             , connect_fun := ConnectFun
-                             , reconnect_delay_ms := Timeout
-                             } = State) ->
-    ok = subscribe_local_topics(Forwards),
-    From = case StateName of
-               standing_by -> {call, Pid} = Type, Pid;
-               connecting -> ?NO_FROM
-           end,
-    DoEvent = fun (standing_by, StandingbyAction, _ConnectingAction) ->
-                      StandingbyAction;
-                  (connecting, _StandingbyAction, ConnectingAction) ->
-                      ConnectingAction
-              end,
-    case ConnectFun(Subs) of
-        {ok, ConnRef, Conn} ->
-            ?LOG(info, "Bridge ~p connected", [name()]),
-            State0 = State#{conn_ref => ConnRef, connection => Conn},
-            State1 = eval_bridge_handler(State0, connected),
-            StandingbyAction = {next_state, connected, State1, [{reply, From, ok}]},
-            ConnectingAction = {keep_state, State1, {state_timeout, 0, connected}},
-            DoEvent(StateName, StandingbyAction, ConnectingAction);
-        {error, Reason} ->
-            StandingbyAction = {keep_state_and_data, [{reply, From, {error, Reason}}]},
-            ConnectingAction = {keep_state_and_data, {state_timeout, Timeout, reconnect}},
-            DoEvent(StateName, StandingbyAction, ConnectingAction)
-    end.
-
-do_ensure_present(forwards, Topic, _) ->
-    ok = subscribe_local_topic(Topic);
-do_ensure_present(subscriptions, _Topic, #{connect_module := _ConnectModule,
-                                           connection := undefined}) ->
-    {error, no_connection};
-do_ensure_present(subscriptions, {Topic, QoS},
-                  #{connect_module := ConnectModule, connection := Conn}) ->
-    case erlang:function_exported(ConnectModule, ensure_subscribed, 3) of
-        true ->
-            _ = ConnectModule:ensure_subscribed(Conn, Topic, QoS),
-            ok;
-        false ->
-            {error, no_remote_subscription_support}
-    end.
-
-do_ensure_absent(forwards, Topic, _) ->
-    ok = emqx_broker:unsubscribe(Topic);
-do_ensure_absent(subscriptions, _Topic, #{connect_module := _ConnectModule,
-                                          connection := undefined}) ->
-    {error, no_connection};
-do_ensure_absent(subscriptions, Topic, #{connect_module := ConnectModule,
-                                         connection := Conn}) ->
-    case erlang:function_exported(ConnectModule, ensure_unsubscribed, 2) of
-        true -> ConnectModule:ensure_unsubscribed(Conn, Topic);
-        false -> {error, no_remote_subscription_support}
-    end.
-
-collect(Acc) ->
-    receive
-        {dispatch, _, Msg} ->
-            collect([Msg | Acc])
-    after
-        0 ->
-            lists:reverse(Acc)
-    end.
-
-%% Retry all inflight (previously sent but not acked) batches.
-retry_inflight(State, []) -> {ok, State};
-retry_inflight(#{inflight := Inflight} = State,
-               [#{q_ack_ref := QAckRef, batch := Batch} | T] = Remain) ->
-    case do_send(State, QAckRef, Batch) of
-        {ok, NewState} ->
-            retry_inflight(NewState, T);
-        {error, Reason} ->
-            ?LOG(error, "Inflight retry failed\n~p", [Reason]),
-            {error, State#{inflight := Inflight ++ Remain}}
-    end.
-
-pop_and_send(#{inflight := Inflight,
-               max_inflight_batches := Max
-              } = State) when length(Inflight) >= Max ->
-    {ok, State};
-pop_and_send(#{replayq := Q,
-               batch_count_limit := CountLimit,
-               batch_bytes_limit := BytesLimit
-              } = State) ->
-    case replayq:is_empty(Q) of
-        true ->
-            {ok, State};
-        false ->
-            Opts = #{count_limit => CountLimit, bytes_limit => BytesLimit},
-            {Q1, QAckRef, Batch} = replayq:pop(Q, Opts),
-            do_send(State#{replayq := Q1}, QAckRef, Batch)
-    end.
-
-%% Assert non-empty batch because we have a is_empty check earlier.
-do_send(State = #{inflight := Inflight}, QAckRef, [_ | _] = Batch) ->
-    case maybe_send(State, Batch) of
-        {ok, Ref} ->
-            %% this is a list of inflight BATCHes, not expecting it to be too long
-            NewInflight = Inflight ++ [#{q_ack_ref => QAckRef,
-                                         send_ack_ref => Ref,
-                                         batch => Batch}],
-            {ok, State#{inflight := NewInflight}};
-        {error, Reason} ->
-            ?LOG(info, "Batch produce failed\n~p", [Reason]),
-            {error, State}
-    end.
-
-do_ack(State = #{inflight := [#{send_ack_ref := Refx, q_ack_ref := QAckRef} | Rest],
-                 replayq := Q}, Ref) when Refx =:= Ref ->
-    ok = replayq:ack(Q, QAckRef),
-    {ok, State#{inflight := Rest}};
-do_ack(#{inflight := Inflight}, Ref) ->
-    case lists:any(fun(#{send_ack_ref := Ref0}) -> Ref0 =:= Ref end, Inflight) of
-        true -> bad_order;
-        false -> stale
-    end.
-
-subscribe_local_topics(Topics) -> lists:foreach(fun subscribe_local_topic/1, Topics).
-
-subscribe_local_topic(Topic0) ->
-    Topic = topic(Topic0),
-    try
-        emqx_topic:validate({filter, Topic})
-    catch
-        error : Reason ->
-            erlang:error({bad_topic, Topic, Reason})
-    end,
-    ok = emqx_broker:subscribe(Topic, #{qos => ?QOS_1, subid => name()}).
-
-topic(T) -> iolist_to_binary(T).
-
-disconnect(#{connection := Conn,
-             conn_ref := ConnRef,
-             connect_module := Module
-            } = State) when Conn =/= undefined ->
-    ok = Module:stop(ConnRef, Conn),
-    State0 = State#{conn_ref => undefined, connection => undefined},
-    eval_bridge_handler(State0, disconnected);
-disconnect(State) ->
-    eval_bridge_handler(State, disconnected).
-
-%% Called only when replayq needs to dump it to disk.
-msg_marshaller(Bin) when is_binary(Bin) -> emqx_bridge_msg:from_binary(Bin);
-msg_marshaller(Msg) -> emqx_bridge_msg:to_binary(Msg).
-
-%% Return {ok, SendAckRef} or {error, Reason}
-maybe_send(#{connect_module := Module,
-             connection := Connection,
-             mountpoint := Mountpoint
-            }, Batch) ->
-    Module:send(Connection, [emqx_bridge_msg:to_export(Module, Mountpoint, M) || M <- Batch]).
-
-format_mountpoint(undefined) ->
-    undefined;
-format_mountpoint(Prefix) ->
-    binary:replace(iolist_to_binary(Prefix), <<"${node}">>, atom_to_binary(node(), utf8)).
-
-name() -> {_, Name} = process_info(self(), registered_name), Name.
-
-name(Id) -> list_to_atom(lists:concat([?MODULE, "_", Id])).
-
-id(Pid) when is_pid(Pid) -> Pid;
-id(Name) -> name(Name).

+ 0 - 73
src/emqx_bridge_connect.erl

@@ -1,73 +0,0 @@
-%% Copyright (c) 2013-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_bridge_connect).
-
--export([start/2]).
-
--export_type([config/0, connection/0]).
-
--optional_callbacks([ensure_subscribed/3, ensure_unsubscribed/2]).
-
-%% map fields depend on implementation
--type config() :: map().
--type connection() :: term().
--type conn_ref() :: term().
--type batch() :: emqx_protal:batch().
--type ack_ref() :: emqx_bridge:ack_ref().
--type topic() :: emqx_topic:topic().
--type qos() :: emqx_mqtt_types:qos().
-
--include("logger.hrl").
-
--logger_header("[Bridge Connect]").
-
-%% establish the connection to remote node/cluster
-%% protal worker (the caller process) should be expecting
-%% a message {disconnected, conn_ref()} when disconnected.
--callback start(config()) -> {ok, conn_ref(), connection()} | {error, any()}.
-
-%% send to remote node/cluster
-%% bridge worker (the caller process) should be expecting
-%% a message {batch_ack, reference()} when batch is acknowledged by remote node/cluster
--callback send(connection(), batch()) -> {ok, ack_ref()} | {error, any()}.
-
-%% called when owner is shutting down.
--callback stop(conn_ref(), connection()) -> ok.
-
--callback ensure_subscribed(connection(), topic(), qos()) -> ok.
-
--callback ensure_unsubscribed(connection(), topic()) -> ok.
-
-start(Module, Config) ->
-    case Module:start(Config) of
-        {ok, Ref, Conn} ->
-            {ok, Ref, Conn};
-        {error, Reason} ->
-            Config1 = obfuscate(Config),
-            ?LOG(error, "Failed to connect with module=~p\n"
-                 "config=~p\nreason:~p", [Module, Config1, Reason]),
-            {error, Reason}
-    end.
-
-obfuscate(Map) ->
-    maps:fold(fun(K, V, Acc) ->
-                      case is_sensitive(K) of
-                          true -> [{K, '***'} | Acc];
-                          false -> [{K, V} | Acc]
-                      end
-              end, [], Map).
-
-is_sensitive(password) -> true;
-is_sensitive(_) -> false.

+ 0 - 198
src/emqx_bridge_mqtt.erl

@@ -1,198 +0,0 @@
-%% Copyright (c) 2013-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 This module implements EMQX Bridge transport layer on top of MQTT protocol
-
--module(emqx_bridge_mqtt).
-
--behaviour(emqx_bridge_connect).
-
-%% behaviour callbacks
--export([ start/1
-        , send/2
-        , stop/2
-        ]).
-
-%% optional behaviour callbacks
--export([ ensure_subscribed/3
-        , ensure_unsubscribed/2
-        ]).
-
--include("emqx_mqtt.hrl").
-
--define(ACK_REF(ClientPid, PktId), {ClientPid, PktId}).
-
-%% Messages towards ack collector process
--define(RANGE(Min, Max), {Min, Max}).
--define(REF_IDS(Ref, Ids), {Ref, Ids}).
--define(SENT(RefIds), {sent, RefIds}).
--define(ACKED(AnyPktId), {acked, AnyPktId}).
--define(STOP(Ref), {stop, Ref}).
-
-%%------------------------------------------------------------------------------
-%% emqx_bridge_connect callbacks
-%%------------------------------------------------------------------------------
-
-start(Config = #{address := Address}) ->
-    Ref = make_ref(),
-    Parent = self(),
-    AckCollector = spawn_link(fun() -> ack_collector(Parent, Ref) end),
-    Handlers = make_hdlr(Parent, AckCollector, Ref),
-    {Host, Port} = case string:tokens(Address, ":") of
-                       [H] -> {H, 1883};
-                       [H, P] -> {H, list_to_integer(P)}
-                   end,
-    ClientConfig = Config#{msg_handler => Handlers,
-                           owner => AckCollector,
-                           host => Host,
-                           port => Port,
-                           bridge_mode => true
-                          },
-    case emqx_client:start_link(ClientConfig) of
-        {ok, Pid} ->
-            case emqx_client:connect(Pid) of
-                {ok, _} ->
-                    try
-                        subscribe_remote_topics(Pid, maps:get(subscriptions, Config, [])),
-                        {ok, Ref, #{ack_collector => AckCollector,
-                                    client_pid => Pid}}
-                    catch
-                        throw : Reason ->
-                            ok = stop(AckCollector, Pid),
-                            {error, Reason}
-                    end;
-                {error, Reason} ->
-                    ok = stop(Ref, #{ack_collector => AckCollector, client_pid => Pid}),
-                    {error, Reason}
-            end;
-        {error, Reason} ->
-            {error, Reason}
-    end.
-
-stop(Ref, #{ack_collector := AckCollector, client_pid := Pid}) ->
-    safe_stop(Pid, fun() -> emqx_client:stop(Pid) end, 1000),
-    safe_stop(AckCollector, fun() -> AckCollector ! ?STOP(Ref) end, 1000),
-    ok.
-
-ensure_subscribed(#{client_pid := Pid}, Topic, QoS) when is_pid(Pid) ->
-    case emqx_client:subscribe(Pid, Topic, QoS) of
-        {ok, _, _} -> ok;
-        Error -> Error
-    end;
-ensure_subscribed(_Conn, _Topic, _QoS) ->
-    %% return ok for now, next re-connect should should call start with new topic added to config
-    ok.
-
-ensure_unsubscribed(#{client_pid := Pid}, Topic) when is_pid(Pid) ->
-    case emqx_client:unsubscribe(Pid, Topic) of
-        {ok, _, _} -> ok;
-        Error -> Error
-    end;
-ensure_unsubscribed(_, _) ->
-    %% return ok for now, next re-connect should should call start with this topic deleted from config
-    ok.
-
-safe_stop(Pid, StopF, Timeout) ->
-    MRef = monitor(process, Pid),
-    unlink(Pid),
-    try
-        StopF()
-    catch
-        _ : _ ->
-            ok
-    end,
-    receive
-        {'DOWN', MRef, _, _, _} ->
-            ok
-    after
-        Timeout ->
-            exit(Pid, kill)
-    end.
-
-send(Conn, Batch) ->
-    send(Conn, Batch, []).
-
-send(#{client_pid := ClientPid, ack_collector := AckCollector} = Conn, [Msg | Rest], Acc) ->
-    case emqx_client:publish(ClientPid, Msg) of
-        {ok, PktId} when Rest =:= [] ->
-            %% last one sent
-            Ref = make_ref(),
-            AckCollector ! ?SENT(?REF_IDS(Ref, lists:reverse([PktId | Acc]))),
-            {ok, Ref};
-        {ok, PktId} ->
-            send(Conn, Rest, [PktId | Acc]);
-        {error, Reason} ->
-            %% NOTE: There is no partial sucess of a batch and recover from the middle
-            %% only to retry all messages in one batch
-            {error, Reason}
-    end.
-
-ack_collector(Parent, ConnRef) ->
-    ack_collector(Parent, ConnRef, queue:new(), []).
-
-ack_collector(Parent, ConnRef, Acked, Sent) ->
-    {NewAcked, NewSent} =
-        receive
-            ?STOP(ConnRef) ->
-                exit(normal);
-            ?ACKED(PktId) ->
-                match_acks(Parent, queue:in(PktId, Acked), Sent);
-            ?SENT(RefIds) ->
-                %% this message only happens per-batch, hence ++ is ok
-                match_acks(Parent, Acked, Sent ++ [RefIds])
-        after
-            200 ->
-                {Acked, Sent}
-        end,
-   ack_collector(Parent, ConnRef, NewAcked, NewSent).
-
-match_acks(_Parent, Acked, []) -> {Acked, []};
-match_acks(Parent, Acked, Sent) ->
-    match_acks_1(Parent, queue:out(Acked), Sent).
-
-match_acks_1(_Parent, {empty, Empty}, Sent) -> {Empty, Sent};
-match_acks_1(Parent, {{value, PktId}, Acked}, [?REF_IDS(Ref, [PktId]) | Sent]) ->
-    %% batch finished
-    ok = emqx_bridge:handle_ack(Parent, Ref),
-    match_acks(Parent, Acked, Sent);
-match_acks_1(Parent, {{value, PktId}, Acked}, [?REF_IDS(Ref, [PktId | RestIds]) | Sent]) ->
-    %% one message finished, but not the whole batch
-    match_acks(Parent, Acked, [?REF_IDS(Ref, RestIds) | Sent]).
-
-
-%% When puback for QoS-1 message is received from remote MQTT broker
-%% NOTE: no support for QoS-2
-handle_puback(AckCollector, #{packet_id := PktId, reason_code := RC}) ->
-    RC =:= ?RC_SUCCESS orelse error({puback_error_code, RC}),
-    AckCollector ! ?ACKED(PktId),
-    ok.
-
-%% Message published from remote broker. Import to local broker.
-import_msg(Msg) ->
-    %% auto-ack should be enabled in emqx_client, hence dummy ack-fun.
-    emqx_bridge:import_batch([Msg], _AckFun = fun() -> ok end).
-
-make_hdlr(Parent, AckCollector, Ref) ->
-    #{puback => fun(Ack) -> handle_puback(AckCollector, Ack) end,
-      publish => fun(Msg) -> import_msg(Msg) end,
-      disconnected => fun(Reason) -> Parent ! {disconnected, Ref, Reason}, ok end
-     }.
-
-subscribe_remote_topics(ClientPid, Subscriptions) ->
-    lists:foreach(fun({Topic, Qos}) ->
-                          case emqx_client:subscribe(ClientPid, Topic, Qos) of
-                              {ok, _, _} -> ok;
-                              Error -> throw(Error)
-                          end
-                  end, Subscriptions).

+ 0 - 84
src/emqx_bridge_msg.erl

@@ -1,84 +0,0 @@
-%% Copyright (c) 2013-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_bridge_msg).
-
--export([ to_binary/1
-        , from_binary/1
-        , to_export/3
-        , to_broker_msgs/1
-        , estimate_size/1
-        ]).
-
--export_type([msg/0]).
-
--include("emqx.hrl").
--include("emqx_mqtt.hrl").
--include("emqx_client.hrl").
-
--type msg() :: emqx_types:message().
--type exp_msg() :: emqx_types:message() | #mqtt_msg{}.
-
-%% @doc Make export format:
-%% 1. Mount topic to a prefix
-%% 2. Fix QoS to 1
-%% @end
-%% Shame that we have to know the callback module here
-%% would be great if we can get rid of #mqtt_msg{} record
-%% and use #message{} in all places.
--spec to_export(emqx_bridge_rpc | emqx_bridge_mqtt,
-                undefined | binary(), msg()) -> exp_msg().
-to_export(emqx_bridge_mqtt, Mountpoint,
-          #message{topic = Topic,
-                   payload = Payload,
-                   flags = Flags
-                  }) ->
-    Retain = maps:get(retain, Flags, false),
-    #mqtt_msg{qos = ?QOS_1,
-              retain = Retain,
-              topic = topic(Mountpoint, Topic),
-              payload = Payload};
-to_export(_Module, Mountpoint,
-          #message{topic = Topic} = Msg) ->
-    Msg#message{topic = topic(Mountpoint, Topic), qos = 1}.
-
-%% @doc Make `binary()' in order to make iodata to be persisted on disk.
--spec to_binary(msg()) -> binary().
-to_binary(Msg) -> term_to_binary(Msg).
-
-%% @doc Unmarshal binary into `msg()'.
--spec from_binary(binary()) -> msg().
-from_binary(Bin) -> binary_to_term(Bin).
-
-%% @doc Estimate the size of a message.
-%% Count only the topic length + payload size
--spec estimate_size(msg()) -> integer().
-estimate_size(#message{topic = Topic, payload = Payload}) ->
-    size(Topic) + size(Payload).
-
-%% @doc By message/batch receiver, transform received batch into
-%% messages to dispatch to local brokers.
-to_broker_msgs(Batch) -> lists:map(fun to_broker_msg/1, Batch).
-
-to_broker_msg(#message{} = Msg) ->
-    %% internal format from another EMQX node via rpc
-    Msg;
-to_broker_msg(#{qos := QoS, dup := Dup, retain := Retain, topic := Topic,
-                properties := Props, payload := Payload}) ->
-    %% published from remote node over a MQTT connection
-    emqx_message:set_headers(Props,
-        emqx_message:set_flags(#{dup => Dup, retain => Retain},
-            emqx_message:make(bridge, QoS, Topic, Payload))).
-
-topic(Prefix, Topic) -> emqx_topic:prepend(Prefix, Topic).

+ 0 - 105
src/emqx_bridge_rpc.erl

@@ -1,105 +0,0 @@
-%% Copyright (c) 2013-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 This module implements EMQX Bridge transport layer based on gen_rpc.
-
--module(emqx_bridge_rpc).
--behaviour(emqx_bridge_connect).
-
-%% behaviour callbacks
--export([ start/1
-        , send/2
-        , stop/2
-        ]).
-
-%% Internal exports
--export([ handle_send/2
-        , handle_ack/2
-        , heartbeat/2
-        ]).
-
--type ack_ref() :: emqx_bridge:ack_ref().
--type batch() :: emqx_bridge:batch().
-
--define(HEARTBEAT_INTERVAL, timer:seconds(1)).
-
--define(RPC, gen_rpc).
-
-start(#{address := Remote}) ->
-    case poke(Remote) of
-        ok ->
-            Pid = proc_lib:spawn_link(?MODULE, heartbeat, [self(), Remote]),
-            {ok, Pid, Remote};
-        Error ->
-            Error
-    end.
-
-stop(Pid, _Remote) when is_pid(Pid) ->
-    Ref = erlang:monitor(process, Pid),
-    unlink(Pid),
-    Pid ! stop,
-    receive
-        {'DOWN', Ref, process, Pid, _Reason} ->
-            ok
-    after
-        1000 ->
-            exit(Pid, kill)
-    end,
-    ok.
-
-%% @doc Callback for `emqx_bridge_connect' behaviour
--spec send(node(), batch()) -> {ok, ack_ref()} | {error, any()}.
-send(Remote, Batch) ->
-    Sender = self(),
-    case ?RPC:call(Remote, ?MODULE, handle_send, [Sender, Batch]) of
-        {ok, Ref} -> {ok, Ref};
-        {badrpc, Reason} -> {error, Reason}
-    end.
-
-%% @doc Handle send on receiver side.
--spec handle_send(pid(), batch()) -> {ok, ack_ref()} | {error, any()}.
-handle_send(SenderPid, Batch) ->
-    SenderNode = node(SenderPid),
-    Ref = make_ref(),
-    AckFun = fun() -> ?RPC:cast(SenderNode, ?MODULE, handle_ack, [SenderPid, Ref]), ok end,
-    case emqx_bridge:import_batch(Batch, AckFun) of
-        ok -> {ok, Ref};
-        Error -> Error
-    end.
-
-%% @doc Handle batch ack in sender node.
-handle_ack(SenderPid, Ref) ->
-    ok = emqx_bridge:handle_ack(SenderPid, Ref).
-
-%% @hidden Heartbeat loop
-heartbeat(Parent, RemoteNode) ->
-    Interval = ?HEARTBEAT_INTERVAL,
-    receive
-        stop -> exit(normal)
-    after
-        Interval ->
-            case poke(RemoteNode) of
-                ok ->
-                    ?MODULE:heartbeat(Parent, RemoteNode);
-                {error, Reason} ->
-                    Parent ! {disconnected, self(), Reason},
-                    exit(normal)
-            end
-    end.
-
-poke(Node) ->
-    case ?RPC:call(Node, erlang, node, []) of
-        Node -> ok;
-        {badrpc, Reason} -> {error, Reason}
-    end.

+ 0 - 81
src/emqx_bridge_sup.erl

@@ -1,81 +0,0 @@
-%% Copyright (c) 2013-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_bridge_sup).
--behavior(supervisor).
-
--include("logger.hrl").
-
--logger_header("[Bridge]").
-
-%% APIs
--export([ start_link/0
-        , start_link/1
-        ]).
-
--export([ create_bridge/2
-        , drop_bridge/1
-        , bridges/0
-        , is_bridge_exist/1
-        ]).
-
-%% supervisor callbacks
--export([init/1]).
-
--define(SUP, ?MODULE).
--define(WORKER_SUP, emqx_bridge_worker_sup).
-
-start_link() -> start_link(?SUP).
-
-start_link(Name) ->
-    supervisor:start_link({local, Name}, ?MODULE, Name).
-
-init(?SUP) ->
-    BridgesConf = emqx_config:get_env(bridges, []),
-    BridgeSpec = lists:map(fun bridge_spec/1, BridgesConf),
-    SupFlag = #{strategy => one_for_one,
-                intensity => 100,
-                period => 10},
-    {ok, {SupFlag, BridgeSpec}}.
-
-bridge_spec({Name, Config}) ->
-    #{id => Name,
-      start => {emqx_bridge, start_link, [Name, Config]},
-      restart => permanent,
-      shutdown => 5000,
-      type => worker,
-      modules => [emqx_bridge]}.
-
--spec(bridges() -> [{node(), map()}]).
-bridges() ->
-    [{Name, emqx_bridge:status(Pid)} || {Name, Pid, _, _} <- supervisor:which_children(?SUP)].
-
--spec(is_bridge_exist(atom() | pid()) -> boolean()).
-is_bridge_exist(Id) ->
-    case supervisor:get_childspec(?SUP, Id) of
-        {ok, _ChildSpec} -> true;
-        {error, _Error} -> false
-    end.
-
-create_bridge(Id, Config) ->
-    supervisor:start_child(?SUP, bridge_spec({Id, Config})).
-
-drop_bridge(Id) ->
-    case supervisor:terminate_child(?SUP, Id) of
-        ok ->
-            supervisor:delete_child(?SUP, Id);
-        Error ->
-            ?LOG(error, "Delete bridge failed, error : ~p", [Error]),
-            Error
-    end.

+ 0 - 2
src/emqx_sup.erl

@@ -65,7 +65,6 @@ init([]) ->
     RouterSup = supervisor_spec(emqx_router_sup),
     %% Broker Sup
     BrokerSup = supervisor_spec(emqx_broker_sup),
-    BridgeSup = supervisor_spec(emqx_bridge_sup),
     %% Session Manager
     SMSup = supervisor_spec(emqx_sm_sup),
     %% Connection Manager
@@ -76,7 +75,6 @@ init([]) ->
           [KernelSup,
            RouterSup,
            BrokerSup,
-           BridgeSup,
            SMSup,
            CMSup,
            SysSup]}}.

+ 0 - 195
test/emqx_bridge_SUITE.erl

@@ -1,195 +0,0 @@
-%% Copyright (c) 2013-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_bridge_SUITE).
-
--export([ all/0
-        , init_per_suite/1
-        , end_per_suite/1]).
--export([ t_rpc/1
-        , t_mqtt/1
-        , t_mngr/1]).
-
--include_lib("eunit/include/eunit.hrl").
--include_lib("common_test/include/ct.hrl").
--include("emqx_mqtt.hrl").
--include("emqx.hrl").
-
--define(wait(For, Timeout), emqx_ct_helpers:wait_for(?FUNCTION_NAME, ?LINE, fun() -> For end, Timeout)).
-
-all() -> [ t_rpc
-         , t_mqtt
-         , t_mngr].
-
-init_per_suite(Config) ->
-    case node() of
-        nonode@nohost -> net_kernel:start(['emqx@127.0.0.1', longnames]);
-        _ -> ok
-    end,
-    emqx_ct_helpers:start_apps([]),
-    emqx_logger:set_log_level(error),
-    [{log_level, error} | Config].
-
-end_per_suite(_Config) ->
-    emqx_ct_helpers:stop_apps([]).
-
-t_mngr(Config) when is_list(Config) ->
-    Subs = [{<<"a">>, 1}, {<<"b">>, 2}],
-    Cfg = #{address => node(),
-            forwards => [<<"mngr">>],
-            connect_module => emqx_bridge_rpc,
-            mountpoint => <<"forwarded">>,
-            subscriptions => Subs,
-            start_type => auto},
-    Name = ?FUNCTION_NAME,
-    {ok, Pid} = emqx_bridge:start_link(Name, Cfg),
-    try
-        ?assertEqual([<<"mngr">>], emqx_bridge:get_forwards(Name)),
-        ?assertEqual(ok, emqx_bridge:ensure_forward_present(Name, "mngr")),
-        ?assertEqual(ok, emqx_bridge:ensure_forward_present(Name, "mngr2")),
-        ?assertEqual([<<"mngr">>, <<"mngr2">>], emqx_bridge:get_forwards(Pid)),
-        ?assertEqual(ok, emqx_bridge:ensure_forward_absent(Name, "mngr2")),
-        ?assertEqual(ok, emqx_bridge:ensure_forward_absent(Name, "mngr3")),
-        ?assertEqual([<<"mngr">>], emqx_bridge:get_forwards(Pid)),
-        ?assertEqual({error, no_remote_subscription_support},
-                     emqx_bridge:ensure_subscription_present(Pid, <<"t">>, 0)),
-        ?assertEqual({error, no_remote_subscription_support},
-                     emqx_bridge:ensure_subscription_absent(Pid, <<"t">>)),
-        ?assertEqual(Subs, emqx_bridge:get_subscriptions(Pid))
-    after
-        ok = emqx_bridge:stop(Pid)
-    end.
-
-%% A loopback RPC to local node
-t_rpc(Config) when is_list(Config) ->
-    Cfg = #{address => node(),
-            forwards => [<<"t_rpc/#">>],
-            connect_module => emqx_bridge_rpc,
-            mountpoint => <<"forwarded">>,
-            start_type => auto},
-    {ok, Pid} = emqx_bridge:start_link(?FUNCTION_NAME, Cfg),
-    ClientId = <<"ClientId">>,
-    try
-        {ok, ConnPid} = emqx_mock_client:start_link(ClientId),
-        {ok, SPid} = emqx_mock_client:open_session(ConnPid, ClientId, internal),
-        %% message from a different client, to avoid getting terminated by no-local
-        Msg1 = emqx_message:make(<<"ClientId-2">>, ?QOS_2, <<"t_rpc/one">>, <<"hello">>),
-        ok = emqx_session:subscribe(SPid, [{<<"forwarded/t_rpc/one">>, #{qos => ?QOS_1}}]),
-        ct:sleep(100),
-        PacketId = 1,
-        emqx_session:publish(SPid, PacketId, Msg1),
-        ?wait(case emqx_mock_client:get_last_message(ConnPid) of
-                  [{publish, PacketId, #message{topic = <<"forwarded/t_rpc/one">>}}] ->
-                      true;
-                  Other ->
-                      Other
-              end, 4000),
-        emqx_mock_client:close_session(ConnPid)
-    after
-        ok = emqx_bridge:stop(Pid)
-    end.
-
-%% Full data loopback flow explained:
-%% test-pid --->  mock-cleint ----> local-broker ---(local-subscription)--->
-%% bridge(export) --- (mqtt-connection)--> local-broker ---(remote-subscription) -->
-%% bridge(import) --(mecked message sending)--> test-pid
-t_mqtt(Config) when is_list(Config) ->
-    SendToTopic = <<"t_mqtt/one">>,
-    SendToTopic2 = <<"t_mqtt/two">>,
-    Mountpoint = <<"forwarded/${node}/">>,
-    ForwardedTopic = emqx_topic:join(["forwarded", atom_to_list(node()), SendToTopic]),
-    ForwardedTopic2 = emqx_topic:join(["forwarded", atom_to_list(node()), SendToTopic2]),
-    Cfg = #{address => "127.0.0.1:1883",
-            forwards => [SendToTopic],
-            connect_module => emqx_bridge_mqtt,
-            mountpoint => Mountpoint,
-            username => "user",
-            clean_start => true,
-            client_id => "bridge_aws",
-            keepalive => 60000,
-            max_inflight => 32,
-            password => "passwd",
-            proto_ver => mqttv4,
-            queue => #{replayq_dir => "data/t_mqtt/",
-                       replayq_seg_bytes => 10000,
-                       batch_bytes_limit => 1000,
-                       batch_count_limit => 10
-                      },
-            reconnect_delay_ms => 1000,
-            ssl => false,
-            %% Consume back to forwarded message for verification
-            %% NOTE: this is a indefenite loopback without mocking emqx_bridge:import_batch/2
-            subscriptions => [{ForwardedTopic, _QoS = 1}],
-            start_type => auto},
-    Tester = self(),
-    Ref = make_ref(),
-    meck:new(emqx_bridge, [passthrough, no_history]),
-    meck:expect(emqx_bridge, import_batch, 2,
-                fun(Batch, AckFun) ->
-                        Tester ! {Ref, Batch},
-                        AckFun()
-                end),
-    {ok, Pid} = emqx_bridge:start_link(?FUNCTION_NAME, Cfg),
-    ClientId = <<"client-1">>,
-    try
-        ?assertEqual([{ForwardedTopic, 1}], emqx_bridge:get_subscriptions(Pid)),
-        ok = emqx_bridge:ensure_subscription_present(Pid, ForwardedTopic2, _QoS = 1),
-        ok = emqx_bridge:ensure_forward_present(Pid, SendToTopic2),
-        ?assertEqual([{ForwardedTopic, 1},
-                      {ForwardedTopic2, 1}], emqx_bridge:get_subscriptions(Pid)),
-        {ok, ConnPid} = emqx_mock_client:start_link(ClientId),
-        {ok, SPid} = emqx_mock_client:open_session(ConnPid, ClientId, internal),
-        %% message from a different client, to avoid getting terminated by no-local
-        Max = 100,
-        Msgs = lists:seq(1, Max),
-        lists:foreach(fun(I) ->
-                          Msg = emqx_message:make(<<"client-2">>, ?QOS_1, SendToTopic, integer_to_binary(I)),
-                          emqx_session:publish(SPid, I, Msg)
-                      end, Msgs),
-        ok = receive_and_match_messages(Ref, Msgs),
-        Msgs2 = lists:seq(Max + 1, Max * 2),
-        lists:foreach(fun(I) ->
-                          Msg = emqx_message:make(<<"client-2">>, ?QOS_1, SendToTopic2, integer_to_binary(I)),
-                          emqx_session:publish(SPid, I, Msg)
-                      end, Msgs2),
-        ok = receive_and_match_messages(Ref, Msgs2),
-        emqx_mock_client:close_session(ConnPid)
-    after
-        ok = emqx_bridge:stop(Pid),
-        meck:unload(emqx_bridge)
-    end.
-
-receive_and_match_messages(Ref, Msgs) ->
-    TRef = erlang:send_after(timer:seconds(5), self(), {Ref, timeout}),
-    try
-        do_receive_and_match_messages(Ref, Msgs)
-    after
-        erlang:cancel_timer(TRef)
-    end,
-    ok.
-
-do_receive_and_match_messages(_Ref, []) -> ok;
-do_receive_and_match_messages(Ref, [I | Rest] = Exp) ->
-    receive
-        {Ref, timeout} -> erlang:error(timeout);
-        {Ref, [#{payload := P} = Msg]} ->
-            case binary_to_integer(P) of
-                I ->  %% exact match
-                    do_receive_and_match_messages(Ref, Rest);
-                J when J < I -> %% allow retry
-                    do_receive_and_match_messages(Ref, Exp);
-                _Other ->
-                    throw({unexpected, Msg, Exp})
-            end
-    end.

+ 0 - 54
test/emqx_bridge_mqtt_tests.erl

@@ -1,54 +0,0 @@
-%% Copyright (c) 2013-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_bridge_mqtt_tests).
--include_lib("eunit/include/eunit.hrl").
--include("emqx_mqtt.hrl").
-
-send_and_ack_test() ->
-    %% delegate from gen_rpc to rpc for unit test
-    meck:new(emqx_client, [passthrough, no_history]),
-    meck:expect(emqx_client, start_link, 1,
-                fun(#{msg_handler := Hdlr}) ->
-                        {ok, spawn_link(fun() -> fake_client(Hdlr) end)}
-                end),
-    meck:expect(emqx_client, connect, 1, {ok, dummy}),
-    meck:expect(emqx_client, stop, 1,
-                fun(Pid) -> Pid ! stop end),
-    meck:expect(emqx_client, publish, 2,
-                fun(Client, Msg) ->
-                        Client ! {publish, Msg},
-                        {ok, Msg} %% as packet id
-                end),
-    try
-        Max = 100,
-        Batch = lists:seq(1, Max),
-        {ok, Ref, Conn} = emqx_bridge_mqtt:start(#{address => "127.0.0.1:1883"}),
-        %% return last packet id as batch reference
-        {ok, AckRef} = emqx_bridge_mqtt:send(Conn, Batch),
-        %% expect batch ack
-        receive {batch_ack, AckRef} -> ok end,
-        ok = emqx_bridge_mqtt:stop(Ref, Conn)
-    after
-        meck:unload(emqx_client)
-    end.
-
-fake_client(#{puback := PubAckCallback} = Hdlr) ->
-    receive
-        {publish, PktId} ->
-            PubAckCallback(#{packet_id => PktId, reason_code => ?RC_SUCCESS}),
-            fake_client(Hdlr);
-        stop ->
-            exit(normal)
-    end.

+ 0 - 43
test/emqx_bridge_rpc_tests.erl

@@ -1,43 +0,0 @@
-%% Copyright (c) 2013-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_bridge_rpc_tests).
--include_lib("eunit/include/eunit.hrl").
-
-send_and_ack_test() ->
-    %% delegate from gen_rpc to rpc for unit test
-    meck:new(gen_rpc, [passthrough, no_history]),
-    meck:expect(gen_rpc, call, 4,
-                fun(Node, Module, Fun, Args) ->
-                        rpc:call(Node, Module, Fun, Args)
-                end),
-    meck:expect(gen_rpc, cast, 4,
-                fun(Node, Module, Fun, Args) ->
-                        rpc:cast(Node, Module, Fun, Args)
-                end),
-    meck:new(emqx_bridge, [passthrough, no_history]),
-    meck:expect(emqx_bridge, import_batch, 2,
-                fun(batch, AckFun) -> AckFun() end),
-    try
-        {ok, Pid, Node} = emqx_bridge_rpc:start(#{address => node()}),
-        {ok, Ref} = emqx_bridge_rpc:send(Node, batch),
-        receive
-            {batch_ack, Ref} ->
-                ok
-        end,
-        ok = emqx_bridge_rpc:stop(Pid, Node)
-    after
-        meck:unload(gen_rpc),
-        meck:unload(emqx_bridge)
-    end.

+ 0 - 155
test/emqx_bridge_tests.erl

@@ -1,155 +0,0 @@
-%% Copyright (c) 2013-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_bridge_tests).
--behaviour(emqx_bridge_connect).
-
--include_lib("eunit/include/eunit.hrl").
--include("emqx.hrl").
--include("emqx_mqtt.hrl").
-
--define(BRIDGE_NAME, test).
--define(BRIDGE_REG_NAME, emqx_bridge_test).
--define(WAIT(PATTERN, TIMEOUT),
-        receive
-            PATTERN ->
-                ok
-        after
-            TIMEOUT ->
-                error(timeout)
-        end).
-
-%% stub callbacks
--export([start/1, send/2, stop/2]).
-
-start(#{connect_result := Result, test_pid := Pid, test_ref := Ref}) ->
-    case is_pid(Pid) of
-        true -> Pid ! {connection_start_attempt, Ref};
-        false -> ok
-    end,
-    Result.
-
-send(SendFun, Batch) when is_function(SendFun, 1) ->
-    SendFun(Batch).
-
-stop(_Ref, _Pid) -> ok.
-
-%% bridge worker should retry connecting remote node indefinitely
-reconnect_test() ->
-    Ref = make_ref(),
-    Config = make_config(Ref, self(), {error, test}),
-    {ok, Pid} = emqx_bridge:start_link(?BRIDGE_NAME, Config),
-    %% assert name registered
-    ?assertEqual(Pid, whereis(?BRIDGE_REG_NAME)),
-    ?WAIT({connection_start_attempt, Ref}, 1000),
-    %% expect same message again
-    ?WAIT({connection_start_attempt, Ref}, 1000),
-    ok = emqx_bridge:stop(?BRIDGE_REG_NAME),
-    ok.
-
-%% connect first, disconnect, then connect again
-disturbance_test() ->
-    Ref = make_ref(),
-    Config = make_config(Ref, self(), {ok, Ref, connection}),
-    {ok, Pid} = emqx_bridge:start_link(?BRIDGE_NAME, Config),
-    ?assertEqual(Pid, whereis(?BRIDGE_REG_NAME)),
-    ?WAIT({connection_start_attempt, Ref}, 1000),
-    Pid ! {disconnected, Ref, test},
-    ?WAIT({connection_start_attempt, Ref}, 1000),
-    ok = emqx_bridge:stop(?BRIDGE_REG_NAME).
-
-%% buffer should continue taking in messages when disconnected
-buffer_when_disconnected_test_() ->
-    {timeout, 10000, fun test_buffer_when_disconnected/0}.
-
-test_buffer_when_disconnected() ->
-    Ref = make_ref(),
-    Nums = lists:seq(1, 100),
-    Sender = spawn_link(fun() -> receive {bridge, Pid} -> sender_loop(Pid, Nums, _Interval = 5) end end),
-    SenderMref = monitor(process, Sender),
-    Receiver = spawn_link(fun() -> receive {bridge, Pid} -> receiver_loop(Pid, Nums, _Interval = 1) end end),
-    ReceiverMref = monitor(process, Receiver),
-    SendFun = fun(Batch) ->
-                      BatchRef = make_ref(),
-                      Receiver ! {batch, BatchRef, Batch},
-                      {ok, BatchRef}
-              end,
-    Config0 = make_config(Ref, false, {ok, Ref, SendFun}),
-    Config = Config0#{reconnect_delay_ms => 100},
-    {ok, Pid} = emqx_bridge:start_link(?BRIDGE_NAME, Config),
-    Sender ! {bridge, Pid},
-    Receiver ! {bridge, Pid},
-    ?assertEqual(Pid, whereis(?BRIDGE_REG_NAME)),
-    Pid ! {disconnected, Ref, test},
-    ?WAIT({'DOWN', SenderMref, process, Sender, normal}, 5000),
-    ?WAIT({'DOWN', ReceiverMref, process, Receiver, normal}, 1000),
-    ok = emqx_bridge:stop(?BRIDGE_REG_NAME).
-
-manual_start_stop_test() ->
-    Ref = make_ref(),
-    Config0 = make_config(Ref, self(), {ok, Ref, connection}),
-    Config = Config0#{start_type := manual},
-    {ok, Pid} = emqx_bridge:ensure_started(?BRIDGE_NAME, Config),
-    %% call ensure_started again should yeld the same result
-    {ok, Pid} = emqx_bridge:ensure_started(?BRIDGE_NAME, Config),
-    ?assertEqual(Pid, whereis(?BRIDGE_REG_NAME)),
-    emqx_bridge:ensure_stopped(unknown),
-    emqx_bridge:ensure_stopped(Pid),
-    emqx_bridge:ensure_stopped(?BRIDGE_REG_NAME).
-
-%% Feed messages to bridge
-sender_loop(_Pid, [], _) -> exit(normal);
-sender_loop(Pid, [Num | Rest], Interval) ->
-    random_sleep(Interval),
-    Pid ! {dispatch, dummy, make_msg(Num)},
-    sender_loop(Pid, Rest, Interval).
-
-%% Feed acknowledgments to bridge
-receiver_loop(_Pid, [], _) -> ok;
-receiver_loop(Pid, Nums, Interval) ->
-    receive
-        {batch, BatchRef, Batch} ->
-            Rest = match_nums(Batch, Nums),
-            random_sleep(Interval),
-            emqx_bridge:handle_ack(Pid, BatchRef),
-            receiver_loop(Pid, Rest, Interval)
-    end.
-
-random_sleep(MaxInterval) ->
-    case rand:uniform(MaxInterval) - 1 of
-        0 -> ok;
-        T -> timer:sleep(T)
-    end.
-
-match_nums([], Rest) -> Rest;
-match_nums([#message{payload = P} | Rest], Nums) ->
-    I = binary_to_integer(P),
-    case Nums of
-        [I | NumsLeft] -> match_nums(Rest, NumsLeft);
-        [J | _] when J > I -> match_nums(Rest, Nums); %% allow retry
-        _ -> error([{received, I}, {expecting, Nums}])
-    end.
-
-make_config(Ref, TestPid, Result) ->
-    #{test_pid => TestPid,
-      test_ref => Ref,
-      connect_module => ?MODULE,
-      reconnect_delay_ms => 50,
-      connect_result => Result,
-      start_type => auto
-     }.
-
-make_msg(I) ->
-    Payload = integer_to_binary(I),
-    emqx_message:make(<<"test/topic">>, Payload).