|
@@ -66,7 +66,6 @@
|
|
|
|
|
|
|
|
%% APIs
|
|
%% APIs
|
|
|
-export([ start_link/1
|
|
-export([ start_link/1
|
|
|
- , start_link/2
|
|
|
|
|
, register_metrics/0
|
|
, register_metrics/0
|
|
|
, stop/1
|
|
, stop/1
|
|
|
]).
|
|
]).
|
|
@@ -86,7 +85,6 @@
|
|
|
%% management APIs
|
|
%% management APIs
|
|
|
-export([ ensure_started/1
|
|
-export([ ensure_started/1
|
|
|
, ensure_stopped/1
|
|
, ensure_stopped/1
|
|
|
- , ensure_stopped/2
|
|
|
|
|
, status/1
|
|
, status/1
|
|
|
]).
|
|
]).
|
|
|
|
|
|
|
@@ -125,14 +123,13 @@
|
|
|
-define(DEFAULT_RECONNECT_DELAY_MS, timer:seconds(5)).
|
|
-define(DEFAULT_RECONNECT_DELAY_MS, timer:seconds(5)).
|
|
|
-define(DEFAULT_SEG_BYTES, (1 bsl 20)).
|
|
-define(DEFAULT_SEG_BYTES, (1 bsl 20)).
|
|
|
-define(DEFAULT_MAX_TOTAL_SIZE, (1 bsl 31)).
|
|
-define(DEFAULT_MAX_TOTAL_SIZE, (1 bsl 31)).
|
|
|
--define(NO_BRIDGE_HANDLER, undefined).
|
|
|
|
|
|
|
|
|
|
%% @doc Start a bridge worker. Supported configs:
|
|
%% @doc Start a bridge worker. Supported configs:
|
|
|
%% start_type: 'manual' (default) or 'auto', when manual, bridge will stay
|
|
%% start_type: 'manual' (default) or 'auto', when manual, bridge will stay
|
|
|
%% at 'idle' state until a manual call to start it.
|
|
%% at 'idle' state until a manual call to start it.
|
|
|
%% connect_module: The module which implements emqx_bridge_connect behaviour
|
|
%% connect_module: The module which implements emqx_bridge_connect behaviour
|
|
|
%% and work as message batch transport layer
|
|
%% and work as message batch transport layer
|
|
|
-%% reconnect_delay_ms: Delay in milli-seconds for the bridge worker to retry
|
|
|
|
|
|
|
+%% reconnect_interval: Delay in milli-seconds for the bridge worker to retry
|
|
|
%% in case of transportation failure.
|
|
%% in case of transportation failure.
|
|
|
%% max_inflight: Max number of batches allowed to send-ahead before receiving
|
|
%% max_inflight: Max number of batches allowed to send-ahead before receiving
|
|
|
%% confirmation from remote node/cluster
|
|
%% confirmation from remote node/cluster
|
|
@@ -148,128 +145,98 @@
|
|
|
%%
|
|
%%
|
|
|
%% Find more connection specific configs in the callback modules
|
|
%% Find more connection specific configs in the callback modules
|
|
|
%% of emqx_bridge_connect behaviour.
|
|
%% of emqx_bridge_connect behaviour.
|
|
|
-start_link(Config) when is_list(Config) ->
|
|
|
|
|
- start_link(maps:from_list(Config));
|
|
|
|
|
-start_link(Config) ->
|
|
|
|
|
- gen_statem:start_link(?MODULE, Config, []).
|
|
|
|
|
-
|
|
|
|
|
-start_link(Name, Config) when is_list(Config) ->
|
|
|
|
|
- start_link(Name, maps:from_list(Config));
|
|
|
|
|
-start_link(Name, Config) ->
|
|
|
|
|
- Name1 = name(Name),
|
|
|
|
|
- gen_statem:start_link({local, Name1}, ?MODULE, Config#{name => Name1}, []).
|
|
|
|
|
|
|
+start_link(Opts) when is_list(Opts) ->
|
|
|
|
|
+ start_link(maps:from_list(Opts));
|
|
|
|
|
+start_link(Opts) ->
|
|
|
|
|
+ case maps:get(name, Opts, undefined) of
|
|
|
|
|
+ undefined ->
|
|
|
|
|
+ gen_statem:start_link(?MODULE, Opts, []);
|
|
|
|
|
+ Name ->
|
|
|
|
|
+ Name1 = name(Name),
|
|
|
|
|
+ gen_statem:start_link({local, Name1}, ?MODULE, Opts#{name => Name1}, [])
|
|
|
|
|
+ end.
|
|
|
|
|
|
|
|
ensure_started(Name) ->
|
|
ensure_started(Name) ->
|
|
|
gen_statem:call(name(Name), ensure_started).
|
|
gen_statem:call(name(Name), ensure_started).
|
|
|
|
|
|
|
|
%% @doc Manually stop bridge worker. State idempotency ensured.
|
|
%% @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.
|
|
|
|
|
|
|
+ensure_stopped(Name) ->
|
|
|
|
|
+ gen_statem:call(name(Name), ensure_stopped, 5000).
|
|
|
|
|
|
|
|
stop(Pid) -> gen_statem:stop(Pid).
|
|
stop(Pid) -> gen_statem:stop(Pid).
|
|
|
|
|
|
|
|
status(Pid) when is_pid(Pid) ->
|
|
status(Pid) when is_pid(Pid) ->
|
|
|
gen_statem:call(Pid, status);
|
|
gen_statem:call(Pid, status);
|
|
|
-status(Id) ->
|
|
|
|
|
- gen_statem:call(name(Id), status).
|
|
|
|
|
|
|
+status(Name) ->
|
|
|
|
|
+ gen_statem:call(name(Name), status).
|
|
|
|
|
|
|
|
%% @doc Return all forwards (local subscriptions).
|
|
%% @doc Return all forwards (local subscriptions).
|
|
|
-spec get_forwards(id()) -> [topic()].
|
|
-spec get_forwards(id()) -> [topic()].
|
|
|
-get_forwards(Id) -> gen_statem:call(id(Id), get_forwards, timer:seconds(1000)).
|
|
|
|
|
|
|
+get_forwards(Name) -> gen_statem:call(name(Name), get_forwards, timer:seconds(1000)).
|
|
|
|
|
|
|
|
%% @doc Return all subscriptions (subscription over mqtt connection to remote broker).
|
|
%% @doc Return all subscriptions (subscription over mqtt connection to remote broker).
|
|
|
-spec get_subscriptions(id()) -> [{emqx_topic:topic(), qos()}].
|
|
-spec get_subscriptions(id()) -> [{emqx_topic:topic(), qos()}].
|
|
|
-get_subscriptions(Id) -> gen_statem:call(id(Id), get_subscriptions).
|
|
|
|
|
|
|
+get_subscriptions(Name) -> gen_statem:call(name(Name), get_subscriptions).
|
|
|
|
|
|
|
|
%% @doc Add a new forward (local topic subscription).
|
|
%% @doc Add a new forward (local topic subscription).
|
|
|
-spec ensure_forward_present(id(), topic()) -> ok.
|
|
-spec ensure_forward_present(id(), topic()) -> ok.
|
|
|
-ensure_forward_present(Id, Topic) ->
|
|
|
|
|
- gen_statem:call(id(Id), {ensure_present, forwards, topic(Topic)}).
|
|
|
|
|
|
|
+ensure_forward_present(Name, Topic) ->
|
|
|
|
|
+ gen_statem:call(name(Name), {ensure_forward_present, topic(Topic)}).
|
|
|
|
|
|
|
|
%% @doc Ensure a forward topic is deleted.
|
|
%% @doc Ensure a forward topic is deleted.
|
|
|
-spec ensure_forward_absent(id(), topic()) -> ok.
|
|
-spec ensure_forward_absent(id(), topic()) -> ok.
|
|
|
-ensure_forward_absent(Id, Topic) ->
|
|
|
|
|
- gen_statem:call(id(Id), {ensure_absent, forwards, topic(Topic)}).
|
|
|
|
|
|
|
+ensure_forward_absent(Name, Topic) ->
|
|
|
|
|
+ gen_statem:call(name(Name), {ensure_forward_absent, topic(Topic)}).
|
|
|
|
|
|
|
|
%% @doc Ensure subscribed to remote topic.
|
|
%% @doc Ensure subscribed to remote topic.
|
|
|
%% NOTE: only applicable when connection module is emqx_bridge_mqtt
|
|
%% NOTE: only applicable when connection module is emqx_bridge_mqtt
|
|
|
%% return `{error, no_remote_subscription_support}' otherwise.
|
|
%% return `{error, no_remote_subscription_support}' otherwise.
|
|
|
-spec ensure_subscription_present(id(), topic(), qos()) -> ok | {error, any()}.
|
|
-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}}).
|
|
|
|
|
|
|
+ensure_subscription_present(Name, Topic, QoS) ->
|
|
|
|
|
+ gen_statem:call(name(Name), {ensure_subscription_present, topic(Topic), QoS}).
|
|
|
|
|
|
|
|
%% @doc Ensure unsubscribed from remote topic.
|
|
%% @doc Ensure unsubscribed from remote topic.
|
|
|
%% NOTE: only applicable when connection module is emqx_bridge_mqtt
|
|
%% NOTE: only applicable when connection module is emqx_bridge_mqtt
|
|
|
-spec ensure_subscription_absent(id(), topic()) -> ok.
|
|
-spec ensure_subscription_absent(id(), topic()) -> ok.
|
|
|
-ensure_subscription_absent(Id, Topic) ->
|
|
|
|
|
- gen_statem:call(id(Id), {ensure_absent, subscriptions, topic(Topic)}).
|
|
|
|
|
|
|
+ensure_subscription_absent(Name, Topic) ->
|
|
|
|
|
+ gen_statem:call(name(Name), {ensure_subscription_absent, topic(Topic)}).
|
|
|
|
|
|
|
|
callback_mode() -> [state_functions].
|
|
callback_mode() -> [state_functions].
|
|
|
|
|
|
|
|
%% @doc Config should be a map().
|
|
%% @doc Config should be a map().
|
|
|
-init(Config) ->
|
|
|
|
|
|
|
+init(Opts) ->
|
|
|
erlang:process_flag(trap_exit, true),
|
|
erlang:process_flag(trap_exit, true),
|
|
|
- ConnectModule = maps:get(connect_module, Config),
|
|
|
|
|
- Subscriptions = maps:get(subscriptions, Config, []),
|
|
|
|
|
- Forwards = maps:get(forwards, Config, []),
|
|
|
|
|
- Queue = open_replayq(Config),
|
|
|
|
|
- State = init_opts(Config),
|
|
|
|
|
- Topics = [iolist_to_binary(T) || T <- Forwards],
|
|
|
|
|
- Subs = check_subscriptions(Subscriptions),
|
|
|
|
|
- ConnectCfg = get_conn_cfg(Config),
|
|
|
|
|
|
|
+ ConnectOpts = maps:get(config, Opts),
|
|
|
|
|
+ ConnectModule = conn_type(maps:get(conn_type, ConnectOpts)),
|
|
|
|
|
+ Forwards = maps:get(forwards, Opts, []),
|
|
|
|
|
+ Queue = open_replayq(maps:get(queue, Opts, #{})),
|
|
|
|
|
+ State = init_opts(Opts),
|
|
|
self() ! idle,
|
|
self() ! idle,
|
|
|
{ok, idle, State#{connect_module => ConnectModule,
|
|
{ok, idle, State#{connect_module => ConnectModule,
|
|
|
- connect_cfg => ConnectCfg,
|
|
|
|
|
- forwards => Topics,
|
|
|
|
|
- subscriptions => Subs,
|
|
|
|
|
|
|
+ connect_opts => ConnectOpts,
|
|
|
|
|
+ forwards => Forwards,
|
|
|
replayq => Queue
|
|
replayq => Queue
|
|
|
}}.
|
|
}}.
|
|
|
|
|
|
|
|
-init_opts(Config) ->
|
|
|
|
|
- IfRecordMetrics = maps:get(if_record_metrics, Config, true),
|
|
|
|
|
- ReconnDelayMs = maps:get(reconnect_delay_ms, Config, ?DEFAULT_RECONNECT_DELAY_MS),
|
|
|
|
|
- StartType = maps:get(start_type, Config, manual),
|
|
|
|
|
- BridgeHandler = maps:get(bridge_handler, Config, ?NO_BRIDGE_HANDLER),
|
|
|
|
|
- Mountpoint = maps:get(forward_mountpoint, Config, undefined),
|
|
|
|
|
- ReceiveMountpoint = maps:get(receive_mountpoint, Config, undefined),
|
|
|
|
|
- MaxInflightSize = maps:get(max_inflight, Config, ?DEFAULT_BATCH_SIZE),
|
|
|
|
|
- BatchSize = maps:get(batch_size, Config, ?DEFAULT_BATCH_SIZE),
|
|
|
|
|
- Name = maps:get(name, Config, undefined),
|
|
|
|
|
|
|
+init_opts(Opts) ->
|
|
|
|
|
+ IfRecordMetrics = maps:get(if_record_metrics, Opts, true),
|
|
|
|
|
+ ReconnDelayMs = maps:get(reconnect_interval, Opts, ?DEFAULT_RECONNECT_DELAY_MS),
|
|
|
|
|
+ StartType = maps:get(start_type, Opts, manual),
|
|
|
|
|
+ Mountpoint = maps:get(forward_mountpoint, Opts, undefined),
|
|
|
|
|
+ MaxInflightSize = maps:get(max_inflight, Opts, ?DEFAULT_BATCH_SIZE),
|
|
|
|
|
+ BatchSize = maps:get(batch_size, Opts, ?DEFAULT_BATCH_SIZE),
|
|
|
|
|
+ Name = maps:get(name, Opts, undefined),
|
|
|
#{start_type => StartType,
|
|
#{start_type => StartType,
|
|
|
- reconnect_delay_ms => ReconnDelayMs,
|
|
|
|
|
|
|
+ reconnect_interval => ReconnDelayMs,
|
|
|
batch_size => BatchSize,
|
|
batch_size => BatchSize,
|
|
|
mountpoint => format_mountpoint(Mountpoint),
|
|
mountpoint => format_mountpoint(Mountpoint),
|
|
|
- receive_mountpoint => ReceiveMountpoint,
|
|
|
|
|
inflight => [],
|
|
inflight => [],
|
|
|
max_inflight => MaxInflightSize,
|
|
max_inflight => MaxInflightSize,
|
|
|
connection => undefined,
|
|
connection => undefined,
|
|
|
- bridge_handler => BridgeHandler,
|
|
|
|
|
if_record_metrics => IfRecordMetrics,
|
|
if_record_metrics => IfRecordMetrics,
|
|
|
name => Name}.
|
|
name => Name}.
|
|
|
|
|
|
|
|
-open_replayq(Config) ->
|
|
|
|
|
- QCfg = maps:get(queue, Config, #{}),
|
|
|
|
|
|
|
+open_replayq(QCfg) ->
|
|
|
Dir = maps:get(replayq_dir, QCfg, undefined),
|
|
Dir = maps:get(replayq_dir, QCfg, undefined),
|
|
|
SegBytes = maps:get(replayq_seg_bytes, QCfg, ?DEFAULT_SEG_BYTES),
|
|
SegBytes = maps:get(replayq_seg_bytes, QCfg, ?DEFAULT_SEG_BYTES),
|
|
|
MaxTotalSize = maps:get(max_total_size, QCfg, ?DEFAULT_MAX_TOTAL_SIZE),
|
|
MaxTotalSize = maps:get(max_total_size, QCfg, ?DEFAULT_MAX_TOTAL_SIZE),
|
|
@@ -280,22 +247,6 @@ open_replayq(Config) ->
|
|
|
replayq:open(QueueConfig#{sizer => fun emqx_bridge_msg:estimate_size/1,
|
|
replayq:open(QueueConfig#{sizer => fun emqx_bridge_msg:estimate_size/1,
|
|
|
marshaller => fun ?MODULE:msg_marshaller/1}).
|
|
marshaller => fun ?MODULE:msg_marshaller/1}).
|
|
|
|
|
|
|
|
-check_subscriptions(Subscriptions) ->
|
|
|
|
|
- lists:map(fun({Topic, QoS}) ->
|
|
|
|
|
- Topic1 = iolist_to_binary(Topic),
|
|
|
|
|
- true = emqx_topic:validate({filter, Topic1}),
|
|
|
|
|
- {Topic1, QoS}
|
|
|
|
|
- end, Subscriptions).
|
|
|
|
|
-
|
|
|
|
|
-get_conn_cfg(Config) ->
|
|
|
|
|
- maps:without([connect_module,
|
|
|
|
|
- queue,
|
|
|
|
|
- reconnect_delay_ms,
|
|
|
|
|
- forwards,
|
|
|
|
|
- mountpoint,
|
|
|
|
|
- name
|
|
|
|
|
- ], Config).
|
|
|
|
|
-
|
|
|
|
|
code_change(_Vsn, State, Data, _Extra) ->
|
|
code_change(_Vsn, State, Data, _Extra) ->
|
|
|
{ok, State, Data}.
|
|
{ok, State, Data}.
|
|
|
|
|
|
|
@@ -321,14 +272,10 @@ idle(info, idle, #{start_type := auto} = State) ->
|
|
|
idle(state_timeout, reconnect, State) ->
|
|
idle(state_timeout, reconnect, State) ->
|
|
|
connecting(State);
|
|
connecting(State);
|
|
|
|
|
|
|
|
-idle(info, {batch_ack, Ref}, State) ->
|
|
|
|
|
- NewState = handle_batch_ack(State, Ref),
|
|
|
|
|
- {keep_state, NewState};
|
|
|
|
|
-
|
|
|
|
|
idle(Type, Content, State) ->
|
|
idle(Type, Content, State) ->
|
|
|
common(idle, Type, Content, State).
|
|
common(idle, Type, Content, State).
|
|
|
|
|
|
|
|
-connecting(#{reconnect_delay_ms := ReconnectDelayMs} = State) ->
|
|
|
|
|
|
|
+connecting(#{reconnect_interval := ReconnectDelayMs} = State) ->
|
|
|
case do_connect(State) of
|
|
case do_connect(State) of
|
|
|
{ok, State1} ->
|
|
{ok, State1} ->
|
|
|
{next_state, connected, State1, {state_timeout, 0, connected}};
|
|
{next_state, connected, State1, {state_timeout, 0, connected}};
|
|
@@ -348,7 +295,7 @@ connected(internal, maybe_send, State) ->
|
|
|
{keep_state, NewState};
|
|
{keep_state, NewState};
|
|
|
|
|
|
|
|
connected(info, {disconnected, Conn, Reason},
|
|
connected(info, {disconnected, Conn, Reason},
|
|
|
- #{connection := Connection, name := Name, reconnect_delay_ms := ReconnectDelayMs} = State) ->
|
|
|
|
|
|
|
+ #{connection := Connection, name := Name, reconnect_interval := ReconnectDelayMs} = State) ->
|
|
|
?tp(info, disconnected, #{name => Name, reason => Reason}),
|
|
?tp(info, disconnected, #{name => Name, reason => Reason}),
|
|
|
case Conn =:= maps:get(client_pid, Connection, undefined) of
|
|
case Conn =:= maps:get(client_pid, Connection, undefined) of
|
|
|
true ->
|
|
true ->
|
|
@@ -365,19 +312,27 @@ connected(Type, Content, State) ->
|
|
|
%% Common handlers
|
|
%% Common handlers
|
|
|
common(StateName, {call, From}, status, _State) ->
|
|
common(StateName, {call, From}, status, _State) ->
|
|
|
{keep_state_and_data, [{reply, From, StateName}]};
|
|
{keep_state_and_data, [{reply, From, StateName}]};
|
|
|
-common(_StateName, {call, From}, ensure_started, _State) ->
|
|
|
|
|
- {keep_state_and_data, [{reply, From, connected}]};
|
|
|
|
|
-common(_StateName, {call, From}, ensure_stopped, _State) ->
|
|
|
|
|
- {stop_and_reply, {shutdown, manual}, [{reply, From, ok}]};
|
|
|
|
|
|
|
+common(_StateName, {call, From}, ensure_stopped, #{connection := undefined} = _State) ->
|
|
|
|
|
+ {keep_state_and_data, [{reply, From, ok}]};
|
|
|
|
|
+common(_StateName, {call, From}, ensure_stopped, #{connection := Conn,
|
|
|
|
|
+ connect_module := ConnectModule} = State) ->
|
|
|
|
|
+ Reply = ConnectModule:stop(Conn),
|
|
|
|
|
+ {next_state, idle, State#{connection => undefined}, [{reply, From, Reply}]};
|
|
|
common(_StateName, {call, From}, get_forwards, #{forwards := Forwards}) ->
|
|
common(_StateName, {call, From}, get_forwards, #{forwards := Forwards}) ->
|
|
|
{keep_state_and_data, [{reply, From, 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),
|
|
|
|
|
|
|
+common(_StateName, {call, From}, get_subscriptions, #{connection := Connection}) ->
|
|
|
|
|
+ {keep_state_and_data, [{reply, From, maps:get(subscriptions, Connection, [])}]};
|
|
|
|
|
+common(_StateName, {call, From}, {ensure_forward_present, Topic}, State) ->
|
|
|
|
|
+ {Result, NewState} = do_ensure_forward_present(Topic, State),
|
|
|
|
|
+ {keep_state, NewState, [{reply, From, Result}]};
|
|
|
|
|
+common(_StateName, {call, From}, {ensure_subscription_present, Topic, QoS}, State) ->
|
|
|
|
|
+ {Result, NewState} = do_ensure_subscription_present(Topic, QoS, State),
|
|
|
|
|
+ {keep_state, NewState, [{reply, From, Result}]};
|
|
|
|
|
+common(_StateName, {call, From}, {ensure_forward_absent, Topic}, State) ->
|
|
|
|
|
+ {Result, NewState} = do_ensure_forward_absent(Topic, State),
|
|
|
{keep_state, NewState, [{reply, From, Result}]};
|
|
{keep_state, NewState, [{reply, From, Result}]};
|
|
|
-common(_StateName, {call, From}, {ensure_absent, What, Topic}, State) ->
|
|
|
|
|
- {Result, NewState} = ensure_absent(What, Topic, State),
|
|
|
|
|
|
|
+common(_StateName, {call, From}, {ensure_subscription_absent, Topic}, State) ->
|
|
|
|
|
+ {Result, NewState} = do_ensure_subscription_absent(Topic, State),
|
|
|
{keep_state, NewState, [{reply, From, Result}]};
|
|
{keep_state, NewState, [{reply, From, Result}]};
|
|
|
common(_StateName, info, {deliver, _, Msg},
|
|
common(_StateName, info, {deliver, _, Msg},
|
|
|
State = #{replayq := Q, if_record_metrics := IfRecordMetric}) ->
|
|
State = #{replayq := Q, if_record_metrics := IfRecordMetric}) ->
|
|
@@ -395,77 +350,79 @@ common(StateName, Type, Content, #{name := Name} = State) ->
|
|
|
[Name, Type, StateName, Content]),
|
|
[Name, Type, StateName, Content]),
|
|
|
{keep_state, State}.
|
|
{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.
|
|
|
|
|
|
|
+do_ensure_forward_present(Topic, #{forwards := Forwards, name := Name} = State) ->
|
|
|
|
|
+ case is_topic_present(Topic, Forwards) of
|
|
|
|
|
+ true ->
|
|
|
|
|
+ {ok, State};
|
|
|
|
|
+ false ->
|
|
|
|
|
+ R = subscribe_local_topic(Topic, Name),
|
|
|
|
|
+ {R, State#{forwards => [Topic | Forwards]}}
|
|
|
|
|
+ end.
|
|
|
|
|
|
|
|
-ensure_present(Key, Topic, State) ->
|
|
|
|
|
- Topics = maps:get(Key, State),
|
|
|
|
|
- case is_topic_present(Topic, Topics) of
|
|
|
|
|
|
|
+do_ensure_subscription_present(_Topic, _QoS, #{connection := undefined} = State) ->
|
|
|
|
|
+ {{error, no_connection}, State};
|
|
|
|
|
+do_ensure_subscription_present(_Topic, _QoS, #{connect_module := emqx_bridge_rpc} = State) ->
|
|
|
|
|
+ {{error, no_remote_subscription_support}, State};
|
|
|
|
|
+do_ensure_subscription_present(Topic, QoS, #{connect_module := ConnectModule,
|
|
|
|
|
+ connection := Conn} = State) ->
|
|
|
|
|
+ case is_topic_present(Topic, maps:get(subscriptions, Conn, [])) of
|
|
|
true ->
|
|
true ->
|
|
|
{ok, State};
|
|
{ok, State};
|
|
|
false ->
|
|
false ->
|
|
|
- R = do_ensure_present(Key, Topic, State),
|
|
|
|
|
- {R, State#{Key := lists:usort([Topic | Topics])}}
|
|
|
|
|
|
|
+ case ConnectModule:ensure_subscribed(Conn, Topic, QoS) of
|
|
|
|
|
+ {error, Error} ->
|
|
|
|
|
+ {{error, Error}, State};
|
|
|
|
|
+ Conn1 ->
|
|
|
|
|
+ {ok, State#{connection => Conn1}}
|
|
|
|
|
+ end
|
|
|
end.
|
|
end.
|
|
|
|
|
|
|
|
-ensure_absent(Key, Topic, State) ->
|
|
|
|
|
- Topics = maps:get(Key, State),
|
|
|
|
|
- case is_topic_present(Topic, Topics) of
|
|
|
|
|
|
|
+do_ensure_forward_absent(Topic, #{forwards := Forwards} = State) ->
|
|
|
|
|
+ case is_topic_present(Topic, Forwards) of
|
|
|
true ->
|
|
true ->
|
|
|
- R = do_ensure_absent(Key, Topic, State),
|
|
|
|
|
- {R, State#{Key := ensure_topic_absent(Topic, Topics)}};
|
|
|
|
|
|
|
+ R = do_unsubscribe(Topic),
|
|
|
|
|
+ {R, State#{forwards => lists:delete(Topic, Forwards)}};
|
|
|
|
|
+ false ->
|
|
|
|
|
+ {ok, State}
|
|
|
|
|
+ end.
|
|
|
|
|
+do_ensure_subscription_absent(_Topic, #{connection := undefined} = State) ->
|
|
|
|
|
+ {{error, no_connection}, State};
|
|
|
|
|
+do_ensure_subscription_absent(_Topic, #{connect_module := emqx_bridge_rpc} = State) ->
|
|
|
|
|
+ {{error, no_remote_subscription_support}, State};
|
|
|
|
|
+do_ensure_subscription_absent(Topic, #{connect_module := ConnectModule,
|
|
|
|
|
+ connection := Conn} = State) ->
|
|
|
|
|
+ case is_topic_present(Topic, maps:get(subscriptions, Conn, [])) of
|
|
|
|
|
+ true ->
|
|
|
|
|
+ case ConnectModule:ensure_unsubscribed(Conn, Topic) of
|
|
|
|
|
+ {error, Error} ->
|
|
|
|
|
+ {{error, Error}, State};
|
|
|
|
|
+ Conn1 ->
|
|
|
|
|
+ {ok, State#{connection => Conn1}}
|
|
|
|
|
+ end;
|
|
|
false ->
|
|
false ->
|
|
|
{ok, State}
|
|
{ok, State}
|
|
|
end.
|
|
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) ->
|
|
is_topic_present(Topic, Topics) ->
|
|
|
lists:member(Topic, Topics) orelse false =/= lists:keyfind(Topic, 1, Topics).
|
|
lists:member(Topic, Topics) orelse false =/= lists:keyfind(Topic, 1, Topics).
|
|
|
|
|
|
|
|
do_connect(#{forwards := Forwards,
|
|
do_connect(#{forwards := Forwards,
|
|
|
- subscriptions := Subs,
|
|
|
|
|
connect_module := ConnectModule,
|
|
connect_module := ConnectModule,
|
|
|
- connect_cfg := ConnectCfg,
|
|
|
|
|
|
|
+ connect_opts := ConnectOpts,
|
|
|
inflight := Inflight,
|
|
inflight := Inflight,
|
|
|
name := Name} = State) ->
|
|
name := Name} = State) ->
|
|
|
ok = subscribe_local_topics(Forwards, Name),
|
|
ok = subscribe_local_topics(Forwards, Name),
|
|
|
- case emqx_bridge_connect:start(ConnectModule, ConnectCfg#{subscriptions => Subs}) of
|
|
|
|
|
|
|
+ case ConnectModule:start(ConnectOpts) of
|
|
|
{ok, Conn} ->
|
|
{ok, Conn} ->
|
|
|
- Res = eval_bridge_handler(State#{connection => Conn}, connected),
|
|
|
|
|
?tp(info, connected, #{name => Name, inflight => length(Inflight)}),
|
|
?tp(info, connected, #{name => Name, inflight => length(Inflight)}),
|
|
|
- {ok, Res};
|
|
|
|
|
|
|
+ {ok, State#{connection => Conn}};
|
|
|
{error, Reason} ->
|
|
{error, Reason} ->
|
|
|
|
|
+ ConnectOpts1 = obfuscate(ConnectOpts),
|
|
|
|
|
+ ?LOG(error, "Failed to connect with module=~p\n"
|
|
|
|
|
+ "config=~p\nreason:~p", [ConnectModule, ConnectOpts1, Reason]),
|
|
|
{error, Reason, State}
|
|
{error, Reason, State}
|
|
|
end.
|
|
end.
|
|
|
|
|
|
|
|
-do_ensure_present(forwards, Topic, #{name := Name}) ->
|
|
|
|
|
- subscribe_local_topic(Topic, Name);
|
|
|
|
|
-do_ensure_present(subscriptions, _Topic, #{connection := undefined}) ->
|
|
|
|
|
- {error, no_connection};
|
|
|
|
|
-do_ensure_present(subscriptions, _Topic, #{connect_module := emqx_bridge_rpc}) ->
|
|
|
|
|
- {error, no_remote_subscription_support};
|
|
|
|
|
-do_ensure_present(subscriptions, {Topic, QoS}, #{connect_module := ConnectModule,
|
|
|
|
|
- connection := Conn}) ->
|
|
|
|
|
- ConnectModule:ensure_subscribed(Conn, Topic, QoS).
|
|
|
|
|
-
|
|
|
|
|
-do_ensure_absent(forwards, Topic, _) ->
|
|
|
|
|
- do_unsubscribe(Topic);
|
|
|
|
|
-do_ensure_absent(subscriptions, _Topic, #{connection := undefined}) ->
|
|
|
|
|
- {error, no_connection};
|
|
|
|
|
-do_ensure_absent(subscriptions, _Topic, #{connect_module := emqx_bridge_rpc}) ->
|
|
|
|
|
- {error, no_remote_subscription_support};
|
|
|
|
|
-do_ensure_absent(subscriptions, Topic, #{connect_module := ConnectModule,
|
|
|
|
|
- connection := Conn}) ->
|
|
|
|
|
- ConnectModule:ensure_unsubscribed(Conn, Topic).
|
|
|
|
|
-
|
|
|
|
|
collect(Acc) ->
|
|
collect(Acc) ->
|
|
|
receive
|
|
receive
|
|
|
{deliver, _, Msg} ->
|
|
{deliver, _, Msg} ->
|
|
@@ -605,10 +562,9 @@ disconnect(#{connection := Conn,
|
|
|
connect_module := Module
|
|
connect_module := Module
|
|
|
} = State) when Conn =/= undefined ->
|
|
} = State) when Conn =/= undefined ->
|
|
|
Module:stop(Conn),
|
|
Module:stop(Conn),
|
|
|
- State0 = State#{connection => undefined},
|
|
|
|
|
- eval_bridge_handler(State0, disconnected);
|
|
|
|
|
|
|
+ State#{connection => undefined};
|
|
|
disconnect(State) ->
|
|
disconnect(State) ->
|
|
|
- eval_bridge_handler(State, disconnected).
|
|
|
|
|
|
|
+ State.
|
|
|
|
|
|
|
|
%% Called only when replayq needs to dump it to disk.
|
|
%% 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(Bin) when is_binary(Bin) -> emqx_bridge_msg:from_binary(Bin);
|
|
@@ -621,9 +577,6 @@ format_mountpoint(Prefix) ->
|
|
|
|
|
|
|
|
name(Id) -> list_to_atom(lists:concat([?MODULE, "_", Id])).
|
|
name(Id) -> list_to_atom(lists:concat([?MODULE, "_", Id])).
|
|
|
|
|
|
|
|
-id(Pid) when is_pid(Pid) -> Pid;
|
|
|
|
|
-id(Name) -> name(Name).
|
|
|
|
|
-
|
|
|
|
|
register_metrics() ->
|
|
register_metrics() ->
|
|
|
lists:foreach(fun emqx_metrics:ensure/1,
|
|
lists:foreach(fun emqx_metrics:ensure/1,
|
|
|
['bridge.mqtt.message_sent',
|
|
['bridge.mqtt.message_sent',
|
|
@@ -639,3 +592,21 @@ bridges_metrics_inc(true, Metric, Value) ->
|
|
|
emqx_metrics:inc(Metric, Value);
|
|
emqx_metrics:inc(Metric, Value);
|
|
|
bridges_metrics_inc(_IsRecordMetric, _Metric, _Value) ->
|
|
bridges_metrics_inc(_IsRecordMetric, _Metric, _Value) ->
|
|
|
ok.
|
|
ok.
|
|
|
|
|
+
|
|
|
|
|
+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.
|
|
|
|
|
+
|
|
|
|
|
+conn_type(rpc) ->
|
|
|
|
|
+ emqx_bridge_rpc;
|
|
|
|
|
+conn_type(mqtt) ->
|
|
|
|
|
+ emqx_bridge_mqtt;
|
|
|
|
|
+conn_type(Mod) when is_atom(Mod) ->
|
|
|
|
|
+ Mod.
|