feat(mqttconn): stop using gproc in hot path
Also drop fiddling with `mountpoint` since this option seems not to be used anywhere.
This commit is contained in:
parent
4da0d83faf
commit
bd956d00b6
|
@ -256,11 +256,11 @@ t_mqtt_egress_bridge_ignores_clean_start(_) ->
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
||||||
{ok, _, #{state := #{name := WorkerName}}} =
|
{ok, _, #{state := #{worker := WorkerPid}}} =
|
||||||
emqx_resource:get_instance(emqx_bridge_resource:resource_id(BridgeID)),
|
emqx_resource:get_instance(emqx_bridge_resource:resource_id(BridgeID)),
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
#{clean_start := true},
|
#{clean_start := true},
|
||||||
maps:from_list(emqx_connector_mqtt_worker:info(WorkerName))
|
maps:from_list(emqx_connector_mqtt_worker:info(WorkerPid))
|
||||||
),
|
),
|
||||||
|
|
||||||
%% delete the bridge
|
%% delete the bridge
|
||||||
|
|
|
@ -25,8 +25,8 @@
|
||||||
callback_mode/0,
|
callback_mode/0,
|
||||||
start_link/0,
|
start_link/0,
|
||||||
init/1,
|
init/1,
|
||||||
create_bridge/1,
|
create_bridge/2,
|
||||||
drop_bridge/1,
|
remove_bridge/1,
|
||||||
bridges/0
|
bridges/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
@ -56,11 +56,10 @@ init([]) ->
|
||||||
},
|
},
|
||||||
{ok, {SupFlag, []}}.
|
{ok, {SupFlag, []}}.
|
||||||
|
|
||||||
bridge_spec(Config) ->
|
bridge_spec(Name, Options) ->
|
||||||
{Name, NConfig} = maps:take(name, Config),
|
|
||||||
#{
|
#{
|
||||||
id => Name,
|
id => Name,
|
||||||
start => {emqx_connector_mqtt_worker, start_link, [Name, NConfig]},
|
start => {emqx_connector_mqtt_worker, start_link, [Name, Options]},
|
||||||
restart => temporary,
|
restart => temporary,
|
||||||
shutdown => 1000
|
shutdown => 1000
|
||||||
}.
|
}.
|
||||||
|
@ -72,10 +71,10 @@ bridges() ->
|
||||||
|| {Name, _Pid, _, _} <- supervisor:which_children(?MODULE)
|
|| {Name, _Pid, _, _} <- supervisor:which_children(?MODULE)
|
||||||
].
|
].
|
||||||
|
|
||||||
create_bridge(Config) ->
|
create_bridge(Name, Options) ->
|
||||||
supervisor:start_child(?MODULE, bridge_spec(Config)).
|
supervisor:start_child(?MODULE, bridge_spec(Name, Options)).
|
||||||
|
|
||||||
drop_bridge(Name) ->
|
remove_bridge(Name) ->
|
||||||
case supervisor:terminate_child(?MODULE, Name) of
|
case supervisor:terminate_child(?MODULE, Name) of
|
||||||
ok ->
|
ok ->
|
||||||
supervisor:delete_child(?MODULE, Name);
|
supervisor:delete_child(?MODULE, Name);
|
||||||
|
@ -95,36 +94,39 @@ on_message_received(Msg, HookPoint, ResId) ->
|
||||||
%% ===================================================================
|
%% ===================================================================
|
||||||
callback_mode() -> async_if_possible.
|
callback_mode() -> async_if_possible.
|
||||||
|
|
||||||
on_start(InstanceId, Conf) ->
|
on_start(ResourceId, Conf) ->
|
||||||
?SLOG(info, #{
|
?SLOG(info, #{
|
||||||
msg => "starting_mqtt_connector",
|
msg => "starting_mqtt_connector",
|
||||||
connector => InstanceId,
|
connector => ResourceId,
|
||||||
config => emqx_utils:redact(Conf)
|
config => emqx_utils:redact(Conf)
|
||||||
}),
|
}),
|
||||||
BasicConf = basic_config(Conf),
|
BasicConf = basic_config(Conf),
|
||||||
BridgeConf = BasicConf#{
|
BridgeOpts = BasicConf#{
|
||||||
name => InstanceId,
|
clientid => clientid(ResourceId, Conf),
|
||||||
clientid => clientid(InstanceId, Conf),
|
subscriptions => make_sub_confs(maps:get(ingress, Conf, #{}), Conf, ResourceId),
|
||||||
subscriptions => make_sub_confs(maps:get(ingress, Conf, undefined), Conf, InstanceId),
|
forwards => maps:get(egress, Conf, #{})
|
||||||
forwards => make_forward_confs(maps:get(egress, Conf, undefined))
|
|
||||||
},
|
},
|
||||||
case ?MODULE:create_bridge(BridgeConf) of
|
case create_bridge(ResourceId, BridgeOpts) of
|
||||||
{ok, _Pid} ->
|
{ok, Pid, {ConnProps, WorkerConf}} ->
|
||||||
ensure_mqtt_worker_started(InstanceId, BridgeConf);
|
{ok, #{
|
||||||
|
name => ResourceId,
|
||||||
|
worker => Pid,
|
||||||
|
config => WorkerConf,
|
||||||
|
props => ConnProps
|
||||||
|
}};
|
||||||
{error, {already_started, _Pid}} ->
|
{error, {already_started, _Pid}} ->
|
||||||
ok = ?MODULE:drop_bridge(InstanceId),
|
ok = remove_bridge(ResourceId),
|
||||||
{ok, _} = ?MODULE:create_bridge(BridgeConf),
|
on_start(ResourceId, Conf);
|
||||||
ensure_mqtt_worker_started(InstanceId, BridgeConf);
|
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{error, Reason}
|
{error, Reason}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
on_stop(_InstId, #{name := InstanceId}) ->
|
on_stop(ResourceId, #{}) ->
|
||||||
?SLOG(info, #{
|
?SLOG(info, #{
|
||||||
msg => "stopping_mqtt_connector",
|
msg => "stopping_mqtt_connector",
|
||||||
connector => InstanceId
|
connector => ResourceId
|
||||||
}),
|
}),
|
||||||
case ?MODULE:drop_bridge(InstanceId) of
|
case remove_bridge(ResourceId) of
|
||||||
ok ->
|
ok ->
|
||||||
ok;
|
ok;
|
||||||
{error, not_found} ->
|
{error, not_found} ->
|
||||||
|
@ -132,24 +134,24 @@ on_stop(_InstId, #{name := InstanceId}) ->
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?SLOG(error, #{
|
?SLOG(error, #{
|
||||||
msg => "stop_mqtt_connector_error",
|
msg => "stop_mqtt_connector_error",
|
||||||
connector => InstanceId,
|
connector => ResourceId,
|
||||||
reason => Reason
|
reason => Reason
|
||||||
})
|
})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
on_query(_InstId, {send_message, Msg}, #{name := InstanceId}) ->
|
on_query(ResourceId, {send_message, Msg}, #{worker := Pid, config := Config}) ->
|
||||||
?TRACE("QUERY", "send_msg_to_remote_node", #{message => Msg, connector => InstanceId}),
|
?TRACE("QUERY", "send_msg_to_remote_node", #{message => Msg, connector => ResourceId}),
|
||||||
case emqx_connector_mqtt_worker:send_to_remote(InstanceId, Msg) of
|
case emqx_connector_mqtt_worker:send_to_remote(Pid, Msg, Config) of
|
||||||
ok ->
|
ok ->
|
||||||
ok;
|
ok;
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
classify_error(Reason)
|
classify_error(Reason)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
on_query_async(_InstId, {send_message, Msg}, CallbackIn, #{name := InstanceId}) ->
|
on_query_async(ResourceId, {send_message, Msg}, CallbackIn, #{worker := Pid, config := Config}) ->
|
||||||
?TRACE("QUERY", "async_send_msg_to_remote_node", #{message => Msg, connector => InstanceId}),
|
?TRACE("QUERY", "async_send_msg_to_remote_node", #{message => Msg, connector => ResourceId}),
|
||||||
Callback = {fun on_async_result/2, [CallbackIn]},
|
Callback = {fun on_async_result/2, [CallbackIn]},
|
||||||
case emqx_connector_mqtt_worker:send_to_remote_async(InstanceId, Msg, Callback) of
|
case emqx_connector_mqtt_worker:send_to_remote_async(Pid, Msg, Callback, Config) of
|
||||||
ok ->
|
ok ->
|
||||||
ok;
|
ok;
|
||||||
{ok, Pid} ->
|
{ok, Pid} ->
|
||||||
|
@ -172,8 +174,8 @@ apply_callback_function({F, A}, Result) when is_function(F), is_list(A) ->
|
||||||
apply_callback_function({M, F, A}, Result) when is_atom(M), is_atom(F), is_list(A) ->
|
apply_callback_function({M, F, A}, Result) when is_atom(M), is_atom(F), is_list(A) ->
|
||||||
erlang:apply(M, F, A ++ [Result]).
|
erlang:apply(M, F, A ++ [Result]).
|
||||||
|
|
||||||
on_get_status(_InstId, #{name := InstanceId}) ->
|
on_get_status(_ResourceId, #{worker := Pid}) ->
|
||||||
emqx_connector_mqtt_worker:status(InstanceId).
|
emqx_connector_mqtt_worker:status(Pid).
|
||||||
|
|
||||||
classify_error(disconnected = Reason) ->
|
classify_error(disconnected = Reason) ->
|
||||||
{error, {recoverable_error, Reason}};
|
{error, {recoverable_error, Reason}};
|
||||||
|
@ -186,33 +188,13 @@ classify_error(shutdown = Reason) ->
|
||||||
classify_error(Reason) ->
|
classify_error(Reason) ->
|
||||||
{error, {unrecoverable_error, Reason}}.
|
{error, {unrecoverable_error, Reason}}.
|
||||||
|
|
||||||
ensure_mqtt_worker_started(InstanceId, BridgeConf) ->
|
make_sub_confs(Subscriptions, _Conf, _) when map_size(Subscriptions) == 0 ->
|
||||||
case emqx_connector_mqtt_worker:connect(InstanceId) of
|
Subscriptions;
|
||||||
{ok, Properties} ->
|
make_sub_confs(Subscriptions, #{hookpoint := HookPoint}, ResourceId) ->
|
||||||
{ok, #{name => InstanceId, config => BridgeConf, props => Properties}};
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end.
|
|
||||||
|
|
||||||
make_sub_confs(EmptyMap, _Conf, _) when map_size(EmptyMap) == 0 ->
|
|
||||||
undefined;
|
|
||||||
make_sub_confs(undefined, _Conf, _) ->
|
|
||||||
undefined;
|
|
||||||
make_sub_confs(SubRemoteConf, Conf, ResourceId) ->
|
|
||||||
case maps:find(hookpoint, Conf) of
|
|
||||||
error ->
|
|
||||||
error({no_hookpoint_provided, Conf});
|
|
||||||
{ok, HookPoint} ->
|
|
||||||
MFA = {?MODULE, on_message_received, [HookPoint, ResourceId]},
|
MFA = {?MODULE, on_message_received, [HookPoint, ResourceId]},
|
||||||
SubRemoteConf#{on_message_received => MFA}
|
Subscriptions#{on_message_received => MFA};
|
||||||
end.
|
make_sub_confs(_SubRemoteConf, Conf, ResourceId) ->
|
||||||
|
error({no_hookpoint_provided, ResourceId, Conf}).
|
||||||
make_forward_confs(EmptyMap) when map_size(EmptyMap) == 0 ->
|
|
||||||
undefined;
|
|
||||||
make_forward_confs(undefined) ->
|
|
||||||
undefined;
|
|
||||||
make_forward_confs(FrowardConf) ->
|
|
||||||
FrowardConf.
|
|
||||||
|
|
||||||
basic_config(
|
basic_config(
|
||||||
#{
|
#{
|
||||||
|
@ -227,17 +209,14 @@ basic_config(
|
||||||
} = Conf
|
} = Conf
|
||||||
) ->
|
) ->
|
||||||
BasicConf = #{
|
BasicConf = #{
|
||||||
%% connection opts
|
|
||||||
server => Server,
|
server => Server,
|
||||||
%% 30s
|
%% 30s
|
||||||
connect_timeout => 30,
|
connect_timeout => 30,
|
||||||
auto_reconnect => true,
|
|
||||||
proto_ver => ProtoVer,
|
proto_ver => ProtoVer,
|
||||||
%% Opening bridge_mode will form a non-standard mqtt connection message.
|
%% Opening a connection in bridge mode will form a non-standard mqtt connection message.
|
||||||
%% A load balancing server (such as haproxy) is often set up before the emqx broker server.
|
%% A load balancing server (such as haproxy) is often set up before the emqx broker server.
|
||||||
%% When the load balancing server enables mqtt connection packet inspection,
|
%% When the load balancing server enables mqtt connection packet inspection,
|
||||||
%% non-standard mqtt connection packets will be filtered out by LB.
|
%% non-standard mqtt connection packets might be filtered out by LB.
|
||||||
%% So let's disable bridge_mode.
|
|
||||||
bridge_mode => BridgeMode,
|
bridge_mode => BridgeMode,
|
||||||
keepalive => ms_to_s(KeepAlive),
|
keepalive => ms_to_s(KeepAlive),
|
||||||
clean_start => CleanStart,
|
clean_start => CleanStart,
|
||||||
|
@ -246,18 +225,9 @@ basic_config(
|
||||||
ssl => EnableSsl,
|
ssl => EnableSsl,
|
||||||
ssl_opts => maps:to_list(maps:remove(enable, Ssl))
|
ssl_opts => maps:to_list(maps:remove(enable, Ssl))
|
||||||
},
|
},
|
||||||
maybe_put_fields([username, password], Conf, BasicConf).
|
maps:merge(
|
||||||
|
BasicConf,
|
||||||
maybe_put_fields(Fields, Conf, Acc0) ->
|
maps:with([username, password], Conf)
|
||||||
lists:foldl(
|
|
||||||
fun(Key, Acc) ->
|
|
||||||
case maps:find(Key, Conf) of
|
|
||||||
error -> Acc;
|
|
||||||
{ok, Val} -> Acc#{Key => Val}
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
Acc0,
|
|
||||||
Fields
|
|
||||||
).
|
).
|
||||||
|
|
||||||
ms_to_s(Ms) ->
|
ms_to_s(Ms) ->
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
-module(emqx_connector_mqtt_msg).
|
-module(emqx_connector_mqtt_msg).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
make_pub_vars/2,
|
|
||||||
to_remote_msg/2,
|
to_remote_msg/2,
|
||||||
to_broker_msg/3
|
to_broker_msg/3
|
||||||
]).
|
]).
|
||||||
|
@ -46,11 +45,6 @@
|
||||||
remote := remote_config()
|
remote := remote_config()
|
||||||
}.
|
}.
|
||||||
|
|
||||||
make_pub_vars(_, undefined) ->
|
|
||||||
undefined;
|
|
||||||
make_pub_vars(Mountpoint, Conf) when is_map(Conf) ->
|
|
||||||
Conf#{mountpoint => Mountpoint}.
|
|
||||||
|
|
||||||
%% @doc Make export format:
|
%% @doc Make export format:
|
||||||
%% 1. Mount topic to a prefix
|
%% 1. Mount topic to a prefix
|
||||||
%% 2. Fix QoS to 1
|
%% 2. Fix QoS to 1
|
||||||
|
@ -70,8 +64,7 @@ to_remote_msg(MapMsg, #{
|
||||||
topic := TopicToken,
|
topic := TopicToken,
|
||||||
qos := QoSToken,
|
qos := QoSToken,
|
||||||
retain := RetainToken
|
retain := RetainToken
|
||||||
} = Remote,
|
} = Remote
|
||||||
mountpoint := Mountpoint
|
|
||||||
}) when is_map(MapMsg) ->
|
}) when is_map(MapMsg) ->
|
||||||
Topic = replace_vars_in_str(TopicToken, MapMsg),
|
Topic = replace_vars_in_str(TopicToken, MapMsg),
|
||||||
Payload = process_payload(Remote, MapMsg),
|
Payload = process_payload(Remote, MapMsg),
|
||||||
|
@ -81,12 +74,10 @@ to_remote_msg(MapMsg, #{
|
||||||
#mqtt_msg{
|
#mqtt_msg{
|
||||||
qos = QoS,
|
qos = QoS,
|
||||||
retain = Retain,
|
retain = Retain,
|
||||||
topic = topic(Mountpoint, Topic),
|
topic = Topic,
|
||||||
props = emqx_utils:pub_props_to_packet(PubProps),
|
props = emqx_utils:pub_props_to_packet(PubProps),
|
||||||
payload = Payload
|
payload = Payload
|
||||||
};
|
}.
|
||||||
to_remote_msg(#message{topic = Topic} = Msg, #{mountpoint := Mountpoint}) ->
|
|
||||||
Msg#message{topic = topic(Mountpoint, Topic)}.
|
|
||||||
|
|
||||||
%% published from remote node over a MQTT connection
|
%% published from remote node over a MQTT connection
|
||||||
to_broker_msg(Msg, Vars, undefined) ->
|
to_broker_msg(Msg, Vars, undefined) ->
|
||||||
|
@ -98,8 +89,7 @@ to_broker_msg(
|
||||||
topic := TopicToken,
|
topic := TopicToken,
|
||||||
qos := QoSToken,
|
qos := QoSToken,
|
||||||
retain := RetainToken
|
retain := RetainToken
|
||||||
} = Local,
|
} = Local
|
||||||
mountpoint := Mountpoint
|
|
||||||
},
|
},
|
||||||
Props
|
Props
|
||||||
) ->
|
) ->
|
||||||
|
@ -112,7 +102,7 @@ to_broker_msg(
|
||||||
Props#{properties => emqx_utils:pub_props_to_packet(PubProps)},
|
Props#{properties => emqx_utils:pub_props_to_packet(PubProps)},
|
||||||
emqx_message:set_flags(
|
emqx_message:set_flags(
|
||||||
#{dup => Dup, retain => Retain},
|
#{dup => Dup, retain => Retain},
|
||||||
emqx_message:make(bridge, QoS, topic(Mountpoint, Topic), Payload)
|
emqx_message:make(bridge, QoS, Topic, Payload)
|
||||||
)
|
)
|
||||||
).
|
).
|
||||||
|
|
||||||
|
@ -142,5 +132,3 @@ replace_simple_var(Val, _Data) ->
|
||||||
|
|
||||||
set_headers(Val, Msg) ->
|
set_headers(Val, Msg) ->
|
||||||
emqx_message:set_headers(Val, Msg).
|
emqx_message:set_headers(Val, Msg).
|
||||||
topic(undefined, Topic) -> Topic;
|
|
||||||
topic(Prefix, Topic) -> emqx_topic:prepend(Prefix, Topic).
|
|
||||||
|
|
|
@ -72,34 +72,75 @@
|
||||||
|
|
||||||
%% management APIs
|
%% management APIs
|
||||||
-export([
|
-export([
|
||||||
connect/1,
|
|
||||||
status/1,
|
status/1,
|
||||||
ping/1,
|
ping/1,
|
||||||
info/1,
|
info/1,
|
||||||
send_to_remote/2,
|
send_to_remote/3,
|
||||||
send_to_remote_async/3
|
send_to_remote_async/4
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([handle_publish/3]).
|
-export([handle_publish/3]).
|
||||||
-export([handle_disconnect/1]).
|
-export([handle_disconnect/1]).
|
||||||
|
|
||||||
-export_type([
|
-export_type([config/0]).
|
||||||
config/0,
|
|
||||||
ack_ref/0
|
-type template() :: emqx_plugin_libs_rule:tmpl_token().
|
||||||
]).
|
|
||||||
|
|
||||||
-type name() :: term().
|
-type name() :: term().
|
||||||
% -type qos() :: emqx_types:qos().
|
-type options() :: #{
|
||||||
-type config() :: map().
|
% endpoint
|
||||||
-type ack_ref() :: term().
|
server := iodata(),
|
||||||
% -type topic() :: emqx_types:topic().
|
% emqtt client options
|
||||||
|
proto_ver := v3 | v4 | v5,
|
||||||
|
username := binary(),
|
||||||
|
password := binary(),
|
||||||
|
clientid := binary(),
|
||||||
|
clean_start := boolean(),
|
||||||
|
max_inflight := pos_integer(),
|
||||||
|
connect_timeout := pos_integer(),
|
||||||
|
retry_interval := timeout(),
|
||||||
|
bridge_mode := boolean(),
|
||||||
|
ssl := boolean(),
|
||||||
|
ssl_opts := proplists:proplist(),
|
||||||
|
% bridge options
|
||||||
|
subscriptions := map(),
|
||||||
|
forwards := map()
|
||||||
|
}.
|
||||||
|
|
||||||
|
-type config() :: #{
|
||||||
|
subscriptions := subscriptions() | undefined,
|
||||||
|
forwards := forwards() | undefined
|
||||||
|
}.
|
||||||
|
|
||||||
|
-type subscriptions() :: #{
|
||||||
|
remote := #{
|
||||||
|
topic := emqx_topic:topic(),
|
||||||
|
qos => emqx_types:qos()
|
||||||
|
},
|
||||||
|
local := #{
|
||||||
|
topic => template(),
|
||||||
|
qos => template() | emqx_types:qos(),
|
||||||
|
retain => template() | boolean(),
|
||||||
|
payload => template() | undefined
|
||||||
|
},
|
||||||
|
on_message_received := {module(), atom(), [term()]}
|
||||||
|
}.
|
||||||
|
|
||||||
|
-type forwards() :: #{
|
||||||
|
local => #{
|
||||||
|
topic => emqx_topic:topic()
|
||||||
|
},
|
||||||
|
remote := #{
|
||||||
|
topic := template(),
|
||||||
|
qos => template() | emqx_types:qos(),
|
||||||
|
retain => template() | boolean(),
|
||||||
|
payload => template() | undefined
|
||||||
|
}
|
||||||
|
}.
|
||||||
|
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
||||||
|
|
||||||
-define(REF(Name), {via, gproc, ?NAME(Name)}).
|
|
||||||
-define(NAME(Name), {n, l, Name}).
|
|
||||||
|
|
||||||
%% @doc Start a bridge worker. Supported configs:
|
%% @doc Start a bridge worker. Supported configs:
|
||||||
%% mountpoint: The topic mount point for messages sent to remote node/cluster
|
%% mountpoint: The topic mount point for messages sent to remote node/cluster
|
||||||
%% `undefined', `<<>>' or `""' to disable
|
%% `undefined', `<<>>' or `""' to disable
|
||||||
|
@ -107,20 +148,19 @@
|
||||||
%%
|
%%
|
||||||
%% 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.
|
||||||
-spec start_link(name(), map()) ->
|
-spec start_link(name(), options()) ->
|
||||||
{ok, pid()} | {error, _Reason}.
|
{ok, pid(), {emqtt:properties(), config()}} | {error, _Reason}.
|
||||||
start_link(Name, BridgeOpts) ->
|
start_link(Name, BridgeOpts) ->
|
||||||
?SLOG(debug, #{
|
?SLOG(debug, #{
|
||||||
msg => "client_starting",
|
msg => "client_starting",
|
||||||
name => Name,
|
name => Name,
|
||||||
options => BridgeOpts
|
options => BridgeOpts
|
||||||
}),
|
}),
|
||||||
Conf = init_config(Name, BridgeOpts),
|
Config = init_config(Name, BridgeOpts),
|
||||||
Options = mk_client_options(Conf, BridgeOpts),
|
Options = mk_client_options(Config, BridgeOpts),
|
||||||
case emqtt:start_link(Options) of
|
case emqtt:start_link(Options) of
|
||||||
{ok, Pid} ->
|
{ok, Pid} ->
|
||||||
true = gproc:reg_other(?NAME(Name), Pid, Conf),
|
connect(Pid, Name, Config);
|
||||||
{ok, Pid};
|
|
||||||
{error, Reason} = Error ->
|
{error, Reason} = Error ->
|
||||||
?SLOG(error, #{
|
?SLOG(error, #{
|
||||||
msg => "client_start_failed",
|
msg => "client_start_failed",
|
||||||
|
@ -130,22 +170,50 @@ start_link(Name, BridgeOpts) ->
|
||||||
Error
|
Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
connect(Pid, Name, Config = #{subscriptions := Subscriptions}) ->
|
||||||
|
case emqtt:connect(Pid) of
|
||||||
|
{ok, Props} ->
|
||||||
|
case subscribe_remote_topics(Pid, Subscriptions) of
|
||||||
|
ok ->
|
||||||
|
{ok, Pid, {Props, Config}};
|
||||||
|
{ok, _, _RCs} ->
|
||||||
|
{ok, Pid, {Props, Config}};
|
||||||
|
{error, Reason} = Error ->
|
||||||
|
?SLOG(error, #{
|
||||||
|
msg => "client_subscribe_failed",
|
||||||
|
subscriptions => Subscriptions,
|
||||||
|
reason => Reason
|
||||||
|
}),
|
||||||
|
_ = emqtt:stop(Pid),
|
||||||
|
Error
|
||||||
|
end;
|
||||||
|
{error, Reason} = Error ->
|
||||||
|
?SLOG(warning, #{
|
||||||
|
msg => "client_connect_failed",
|
||||||
|
reason => Reason,
|
||||||
|
name => Name
|
||||||
|
}),
|
||||||
|
_ = emqtt:stop(Pid),
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
subscribe_remote_topics(Pid, #{remote := #{topic := RemoteTopic, qos := QoS}}) ->
|
||||||
|
emqtt:subscribe(Pid, RemoteTopic, QoS);
|
||||||
|
subscribe_remote_topics(_Ref, undefined) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
init_config(Name, Opts) ->
|
init_config(Name, Opts) ->
|
||||||
Mountpoint = maps:get(forward_mountpoint, Opts, undefined),
|
|
||||||
Subscriptions = maps:get(subscriptions, Opts, undefined),
|
Subscriptions = maps:get(subscriptions, Opts, undefined),
|
||||||
Forwards = maps:get(forwards, Opts, undefined),
|
Forwards = maps:get(forwards, Opts, undefined),
|
||||||
#{
|
#{
|
||||||
mountpoint => format_mountpoint(Mountpoint),
|
|
||||||
subscriptions => pre_process_subscriptions(Subscriptions, Name, Opts),
|
subscriptions => pre_process_subscriptions(Subscriptions, Name, Opts),
|
||||||
forwards => pre_process_forwards(Forwards)
|
forwards => pre_process_forwards(Forwards)
|
||||||
}.
|
}.
|
||||||
|
|
||||||
mk_client_options(Conf, BridgeOpts) ->
|
mk_client_options(Config, BridgeOpts) ->
|
||||||
Server = iolist_to_binary(maps:get(server, BridgeOpts)),
|
Server = iolist_to_binary(maps:get(server, BridgeOpts)),
|
||||||
HostPort = emqx_connector_mqtt_schema:parse_server(Server),
|
HostPort = emqx_connector_mqtt_schema:parse_server(Server),
|
||||||
Mountpoint = maps:get(receive_mountpoint, BridgeOpts, undefined),
|
Subscriptions = maps:get(subscriptions, Config),
|
||||||
Subscriptions = maps:get(subscriptions, Conf),
|
|
||||||
Vars = emqx_connector_mqtt_msg:make_pub_vars(Mountpoint, Subscriptions),
|
|
||||||
CleanStart =
|
CleanStart =
|
||||||
case Subscriptions of
|
case Subscriptions of
|
||||||
#{remote := _} ->
|
#{remote := _} ->
|
||||||
|
@ -156,24 +224,26 @@ mk_client_options(Conf, BridgeOpts) ->
|
||||||
%% to ensure proper session recovery according to the MQTT spec.
|
%% to ensure proper session recovery according to the MQTT spec.
|
||||||
true
|
true
|
||||||
end,
|
end,
|
||||||
Opts = maps:without(
|
Opts = maps:with(
|
||||||
[
|
[
|
||||||
address,
|
proto_ver,
|
||||||
auto_reconnect,
|
username,
|
||||||
conn_type,
|
password,
|
||||||
mountpoint,
|
clientid,
|
||||||
forwards,
|
max_inflight,
|
||||||
receive_mountpoint,
|
connect_timeout,
|
||||||
subscriptions
|
retry_interval,
|
||||||
|
bridge_mode,
|
||||||
|
ssl,
|
||||||
|
ssl_opts
|
||||||
],
|
],
|
||||||
BridgeOpts
|
BridgeOpts
|
||||||
),
|
),
|
||||||
Opts#{
|
Opts#{
|
||||||
msg_handler => mk_client_event_handler(Vars, #{server => Server}),
|
msg_handler => mk_client_event_handler(Subscriptions, #{server => Server}),
|
||||||
hosts => [HostPort],
|
hosts => [HostPort],
|
||||||
clean_start => CleanStart,
|
clean_start => CleanStart,
|
||||||
force_ping => true,
|
force_ping => true
|
||||||
proto_ver => maps:get(proto_ver, BridgeOpts, v4)
|
|
||||||
}.
|
}.
|
||||||
|
|
||||||
mk_client_event_handler(Vars, Opts) when Vars /= undefined ->
|
mk_client_event_handler(Vars, Opts) when Vars /= undefined ->
|
||||||
|
@ -184,45 +254,15 @@ mk_client_event_handler(Vars, Opts) when Vars /= undefined ->
|
||||||
mk_client_event_handler(undefined, _Opts) ->
|
mk_client_event_handler(undefined, _Opts) ->
|
||||||
undefined.
|
undefined.
|
||||||
|
|
||||||
connect(Name) ->
|
stop(Pid) ->
|
||||||
#{subscriptions := Subscriptions} = get_config(Name),
|
emqtt:stop(Pid).
|
||||||
case emqtt:connect(get_pid(Name)) of
|
|
||||||
{ok, Properties} ->
|
|
||||||
case subscribe_remote_topics(Name, Subscriptions) of
|
|
||||||
ok ->
|
|
||||||
{ok, Properties};
|
|
||||||
{ok, _, _RCs} ->
|
|
||||||
{ok, Properties};
|
|
||||||
{error, Reason} = Error ->
|
|
||||||
?SLOG(error, #{
|
|
||||||
msg => "client_subscribe_failed",
|
|
||||||
subscriptions => Subscriptions,
|
|
||||||
reason => Reason
|
|
||||||
}),
|
|
||||||
Error
|
|
||||||
end;
|
|
||||||
{error, Reason} = Error ->
|
|
||||||
?SLOG(warning, #{
|
|
||||||
msg => "client_connect_failed",
|
|
||||||
reason => Reason,
|
|
||||||
name => Name
|
|
||||||
}),
|
|
||||||
Error
|
|
||||||
end.
|
|
||||||
subscribe_remote_topics(Ref, #{remote := #{topic := FromTopic, qos := QoS}}) ->
|
|
||||||
emqtt:subscribe(ref(Ref), FromTopic, QoS);
|
|
||||||
subscribe_remote_topics(_Ref, undefined) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
stop(Ref) ->
|
info(Pid) ->
|
||||||
emqtt:stop(ref(Ref)).
|
emqtt:info(Pid).
|
||||||
|
|
||||||
info(Ref) ->
|
status(Pid) ->
|
||||||
emqtt:info(ref(Ref)).
|
|
||||||
|
|
||||||
status(Ref) ->
|
|
||||||
try
|
try
|
||||||
case proplists:get_value(socket, info(Ref)) of
|
case proplists:get_value(socket, info(Pid)) of
|
||||||
Socket when Socket /= undefined ->
|
Socket when Socket /= undefined ->
|
||||||
connected;
|
connected;
|
||||||
undefined ->
|
undefined ->
|
||||||
|
@ -233,14 +273,14 @@ status(Ref) ->
|
||||||
disconnected
|
disconnected
|
||||||
end.
|
end.
|
||||||
|
|
||||||
ping(Ref) ->
|
ping(Pid) ->
|
||||||
emqtt:ping(ref(Ref)).
|
emqtt:ping(Pid).
|
||||||
|
|
||||||
send_to_remote(Name, MsgIn) ->
|
send_to_remote(Pid, MsgIn, Conf) ->
|
||||||
trycall(fun() -> do_send(Name, export_msg(Name, MsgIn)) end).
|
do_send(Pid, export_msg(MsgIn, Conf)).
|
||||||
|
|
||||||
do_send(Name, {true, Msg}) ->
|
do_send(Pid, {true, Msg}) ->
|
||||||
case emqtt:publish(get_pid(Name), Msg) of
|
case emqtt:publish(Pid, Msg) of
|
||||||
ok ->
|
ok ->
|
||||||
ok;
|
ok;
|
||||||
{ok, #{reason_code := RC}} when
|
{ok, #{reason_code := RC}} when
|
||||||
|
@ -266,36 +306,15 @@ do_send(Name, {true, Msg}) ->
|
||||||
do_send(_Name, false) ->
|
do_send(_Name, false) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
send_to_remote_async(Name, MsgIn, Callback) ->
|
send_to_remote_async(Pid, MsgIn, Callback, Conf) ->
|
||||||
trycall(fun() -> do_send_async(Name, export_msg(Name, MsgIn), Callback) end).
|
do_send_async(Pid, export_msg(MsgIn, Conf), Callback).
|
||||||
|
|
||||||
do_send_async(Name, {true, Msg}, Callback) ->
|
do_send_async(Pid, {true, Msg}, Callback) ->
|
||||||
Pid = get_pid(Name),
|
|
||||||
ok = emqtt:publish_async(Pid, Msg, _Timeout = infinity, Callback),
|
ok = emqtt:publish_async(Pid, Msg, _Timeout = infinity, Callback),
|
||||||
{ok, Pid};
|
{ok, Pid};
|
||||||
do_send_async(_Name, false, _Callback) ->
|
do_send_async(_Pid, false, _Callback) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
ref(Pid) when is_pid(Pid) ->
|
|
||||||
Pid;
|
|
||||||
ref(Term) ->
|
|
||||||
?REF(Term).
|
|
||||||
|
|
||||||
trycall(Fun) ->
|
|
||||||
try
|
|
||||||
Fun()
|
|
||||||
catch
|
|
||||||
throw:noproc ->
|
|
||||||
{error, disconnected};
|
|
||||||
exit:{noproc, _} ->
|
|
||||||
{error, disconnected}
|
|
||||||
end.
|
|
||||||
|
|
||||||
format_mountpoint(undefined) ->
|
|
||||||
undefined;
|
|
||||||
format_mountpoint(Prefix) ->
|
|
||||||
binary:replace(iolist_to_binary(Prefix), <<"${node}">>, atom_to_binary(node(), utf8)).
|
|
||||||
|
|
||||||
pre_process_subscriptions(undefined, _, _) ->
|
pre_process_subscriptions(undefined, _, _) ->
|
||||||
undefined;
|
undefined;
|
||||||
pre_process_subscriptions(
|
pre_process_subscriptions(
|
||||||
|
@ -356,38 +375,15 @@ downgrade_ingress_qos(2) ->
|
||||||
downgrade_ingress_qos(QoS) ->
|
downgrade_ingress_qos(QoS) ->
|
||||||
QoS.
|
QoS.
|
||||||
|
|
||||||
get_pid(Name) ->
|
export_msg(Msg, #{forwards := Forwards = #{}}) ->
|
||||||
case gproc:where(?NAME(Name)) of
|
{true, emqx_connector_mqtt_msg:to_remote_msg(Msg, Forwards)};
|
||||||
Pid when is_pid(Pid) ->
|
export_msg(Msg, #{forwards := undefined}) ->
|
||||||
Pid;
|
|
||||||
undefined ->
|
|
||||||
throw(noproc)
|
|
||||||
end.
|
|
||||||
|
|
||||||
get_config(Name) ->
|
|
||||||
try
|
|
||||||
gproc:lookup_value(?NAME(Name))
|
|
||||||
catch
|
|
||||||
error:badarg ->
|
|
||||||
throw(noproc)
|
|
||||||
end.
|
|
||||||
|
|
||||||
export_msg(Name, Msg) ->
|
|
||||||
case get_config(Name) of
|
|
||||||
#{forwards := Forwards = #{}, mountpoint := Mountpoint} ->
|
|
||||||
{true, export_msg(Mountpoint, Forwards, Msg)};
|
|
||||||
#{forwards := undefined} ->
|
|
||||||
?SLOG(error, #{
|
?SLOG(error, #{
|
||||||
msg => "forwarding_unavailable",
|
msg => "forwarding_unavailable",
|
||||||
message => Msg,
|
message => Msg,
|
||||||
reason => "egress is not configured"
|
reason => "egress is not configured"
|
||||||
}),
|
}),
|
||||||
false
|
false.
|
||||||
end.
|
|
||||||
|
|
||||||
export_msg(Mountpoint, Forwards, Msg) ->
|
|
||||||
Vars = emqx_connector_mqtt_msg:make_pub_vars(Mountpoint, Forwards),
|
|
||||||
emqx_connector_mqtt_msg:to_remote_msg(Msg, Vars).
|
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue