Merge pull request #6473 from terry-xiaoyu/fix_connector_apis
fix(connector): add type and name in repsonse of GET /connectors
This commit is contained in:
commit
b2e7a6a249
|
@ -8,9 +8,9 @@
|
||||||
# connector = "mqtt:my_mqtt_connector"
|
# connector = "mqtt:my_mqtt_connector"
|
||||||
# direction = ingress
|
# direction = ingress
|
||||||
# ## topic mappings for this bridge
|
# ## topic mappings for this bridge
|
||||||
# from_remote_topic = "aws/#"
|
# remote_topic = "aws/#"
|
||||||
# subscribe_qos = 1
|
# subscribe_qos = 1
|
||||||
# to_local_topic = "from_aws/${topic}"
|
# local_topic = "from_aws/${topic}"
|
||||||
# payload = "${payload}"
|
# payload = "${payload}"
|
||||||
# qos = "${qos}"
|
# qos = "${qos}"
|
||||||
# retain = "${retain}"
|
# retain = "${retain}"
|
||||||
|
@ -21,8 +21,8 @@
|
||||||
# connector = "mqtt:my_mqtt_connector"
|
# connector = "mqtt:my_mqtt_connector"
|
||||||
# direction = egress
|
# direction = egress
|
||||||
# ## topic mappings for this bridge
|
# ## topic mappings for this bridge
|
||||||
# from_local_topic = "emqx/#"
|
# local_topic = "emqx/#"
|
||||||
# to_remote_topic = "from_emqx/${topic}"
|
# remote_topic = "from_emqx/${topic}"
|
||||||
# payload = "${payload}"
|
# payload = "${payload}"
|
||||||
# qos = 1
|
# qos = 1
|
||||||
# retain = false
|
# retain = false
|
||||||
|
|
|
@ -67,9 +67,16 @@ load_hook(Bridges) ->
|
||||||
end, maps:to_list(Bridge))
|
end, maps:to_list(Bridge))
|
||||||
end, maps:to_list(Bridges)).
|
end, maps:to_list(Bridges)).
|
||||||
|
|
||||||
do_load_hook(#{from_local_topic := _}) ->
|
do_load_hook(#{local_topic := _} = Conf) ->
|
||||||
emqx_hooks:put('message.publish', {?MODULE, on_message_publish, []}),
|
case maps:find(direction, Conf) of
|
||||||
ok;
|
error ->
|
||||||
|
%% this bridge has no direction field, it means that it has only egress bridges
|
||||||
|
emqx_hooks:put('message.publish', {?MODULE, on_message_publish, []});
|
||||||
|
{ok, egress} ->
|
||||||
|
emqx_hooks:put('message.publish', {?MODULE, on_message_publish, []});
|
||||||
|
{ok, ingress} ->
|
||||||
|
ok
|
||||||
|
end;
|
||||||
do_load_hook(_Conf) -> ok.
|
do_load_hook(_Conf) -> ok.
|
||||||
|
|
||||||
unload_hook() ->
|
unload_hook() ->
|
||||||
|
@ -218,7 +225,7 @@ recreate(Type, Name, Conf) ->
|
||||||
emqx_bridge:resource_type(Type), parse_confs(Type, Name, Conf), []).
|
emqx_bridge:resource_type(Type), parse_confs(Type, Name, Conf), []).
|
||||||
|
|
||||||
create_dry_run(Type, Conf) ->
|
create_dry_run(Type, Conf) ->
|
||||||
Conf0 = Conf#{<<"ingress">> => #{<<"from_remote_topic">> => <<"t">>}},
|
Conf0 = Conf#{<<"ingress">> => #{<<"remote_topic">> => <<"t">>}},
|
||||||
case emqx_resource:check_config(emqx_bridge:resource_type(Type), Conf0) of
|
case emqx_resource:check_config(emqx_bridge:resource_type(Type), Conf0) of
|
||||||
{ok, Conf1} ->
|
{ok, Conf1} ->
|
||||||
emqx_resource:create_dry_run_local(emqx_bridge:resource_type(Type), Conf1);
|
emqx_resource:create_dry_run_local(emqx_bridge:resource_type(Type), Conf1);
|
||||||
|
@ -263,7 +270,7 @@ get_matched_bridges(Topic) ->
|
||||||
end, Acc0, Conf)
|
end, Acc0, Conf)
|
||||||
end, [], Bridges).
|
end, [], Bridges).
|
||||||
|
|
||||||
get_matched_bridge_id(#{from_local_topic := Filter}, Topic, BType, BName, Acc) ->
|
get_matched_bridge_id(#{local_topic := Filter}, Topic, BType, BName, Acc) ->
|
||||||
case emqx_topic:match(Topic, Filter) of
|
case emqx_topic:match(Topic, Filter) of
|
||||||
true -> [bridge_id(BType, BName) | Acc];
|
true -> [bridge_id(BType, BName) | Acc];
|
||||||
false -> Acc
|
false -> Acc
|
||||||
|
|
|
@ -161,7 +161,7 @@ info_example_basic(http, _) ->
|
||||||
pool_size => 4,
|
pool_size => 4,
|
||||||
enable_pipelining => true,
|
enable_pipelining => true,
|
||||||
ssl => #{enable => false},
|
ssl => #{enable => false},
|
||||||
from_local_topic => <<"emqx_http/#">>,
|
local_topic => <<"emqx_http/#">>,
|
||||||
method => post,
|
method => post,
|
||||||
body => <<"${payload}">>
|
body => <<"${payload}">>
|
||||||
};
|
};
|
||||||
|
@ -169,21 +169,21 @@ info_example_basic(mqtt, ingress) ->
|
||||||
#{
|
#{
|
||||||
connector => <<"mqtt:my_mqtt_connector">>,
|
connector => <<"mqtt:my_mqtt_connector">>,
|
||||||
direction => ingress,
|
direction => ingress,
|
||||||
from_remote_topic => <<"aws/#">>,
|
remote_topic => <<"aws/#">>,
|
||||||
subscribe_qos => 1,
|
remote_qos => 1,
|
||||||
to_local_topic => <<"from_aws/${topic}">>,
|
local_topic => <<"from_aws/${topic}">>,
|
||||||
|
local_qos => <<"${qos}">>,
|
||||||
payload => <<"${payload}">>,
|
payload => <<"${payload}">>,
|
||||||
qos => <<"${qos}">>,
|
|
||||||
retain => <<"${retain}">>
|
retain => <<"${retain}">>
|
||||||
};
|
};
|
||||||
info_example_basic(mqtt, egress) ->
|
info_example_basic(mqtt, egress) ->
|
||||||
#{
|
#{
|
||||||
connector => <<"mqtt:my_mqtt_connector">>,
|
connector => <<"mqtt:my_mqtt_connector">>,
|
||||||
direction => egress,
|
direction => egress,
|
||||||
from_local_topic => <<"emqx/#">>,
|
local_topic => <<"emqx/#">>,
|
||||||
to_remote_topic => <<"from_emqx/${topic}">>,
|
remote_topic => <<"from_emqx/${topic}">>,
|
||||||
|
remote_qos => <<"${qos}">>,
|
||||||
payload => <<"${payload}">>,
|
payload => <<"${payload}">>,
|
||||||
qos => 1,
|
|
||||||
retain => false
|
retain => false
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,12 @@ For example, <code> http://localhost:9901/${topic} </code> is allowed, but
|
||||||
is not allowed.
|
is not allowed.
|
||||||
"""
|
"""
|
||||||
})}
|
})}
|
||||||
, {from_local_topic, mk(binary(),
|
, {local_topic, mk(binary(),
|
||||||
#{ desc =>"""
|
#{ desc =>"""
|
||||||
The MQTT topic filter to be forwarded to the HTTP server. All MQTT PUBLISH messages which topic
|
The MQTT topic filter to be forwarded to the HTTP server. All MQTT PUBLISH messages which topic
|
||||||
match the from_local_topic will be forwarded.<br>
|
match the local_topic will be forwarded.<br>
|
||||||
NOTE: if this bridge is used as the output of a rule (emqx rule engine), and also from_local_topic is configured, then both the data got from the rule and the MQTT messages that matches
|
NOTE: if this bridge is used as the output of a rule (emqx rule engine), and also local_topic is configured, then both the data got from the rule and the MQTT messages that matches
|
||||||
from_local_topic will be forwarded.
|
local_topic will be forwarded.
|
||||||
"""
|
"""
|
||||||
})}
|
})}
|
||||||
, {method, mk(method(),
|
, {method, mk(method(),
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
-define(HTTP_BRIDGE(URL),
|
-define(HTTP_BRIDGE(URL),
|
||||||
#{
|
#{
|
||||||
<<"url">> => URL,
|
<<"url">> => URL,
|
||||||
<<"from_local_topic">> => <<"emqx_http/#">>,
|
<<"local_topic">> => <<"emqx_http/#">>,
|
||||||
<<"method">> => <<"post">>,
|
<<"method">> => <<"post">>,
|
||||||
<<"ssl">> => #{<<"enable">> => false},
|
<<"ssl">> => #{<<"enable">> => false},
|
||||||
<<"body">> => <<"${payload}">>,
|
<<"body">> => <<"${payload}">>,
|
||||||
|
|
|
@ -209,7 +209,7 @@ schema("/connectors/:id") ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
'/connectors'(get, _Request) ->
|
'/connectors'(get, _Request) ->
|
||||||
{200, emqx_connector:list()};
|
{200, [format_resp(Conn) || Conn <- emqx_connector:list()]};
|
||||||
|
|
||||||
'/connectors'(post, #{body := #{<<"type">> := ConnType} = Params}) ->
|
'/connectors'(post, #{body := #{<<"type">> := ConnType} = Params}) ->
|
||||||
ConnName = maps:get(<<"name">>, Params, emqx_misc:gen_id()),
|
ConnName = maps:get(<<"name">>, Params, emqx_misc:gen_id()),
|
||||||
|
@ -264,10 +264,16 @@ error_msg(Code, Msg) when is_binary(Msg) ->
|
||||||
error_msg(Code, Msg) ->
|
error_msg(Code, Msg) ->
|
||||||
#{code => Code, message => bin(io_lib:format("~p", [Msg]))}.
|
#{code => Code, message => bin(io_lib:format("~p", [Msg]))}.
|
||||||
|
|
||||||
|
format_resp(#{<<"id">> := Id} = RawConf) ->
|
||||||
|
format_resp(Id, RawConf).
|
||||||
|
|
||||||
format_resp(ConnId, RawConf) ->
|
format_resp(ConnId, RawConf) ->
|
||||||
NumOfBridges = length(emqx_bridge:list_bridges_by_connector(ConnId)),
|
NumOfBridges = length(emqx_bridge:list_bridges_by_connector(ConnId)),
|
||||||
|
{Type, Name} = emqx_connector:parse_connector_id(ConnId),
|
||||||
RawConf#{
|
RawConf#{
|
||||||
<<"id">> => ConnId,
|
<<"id">> => ConnId,
|
||||||
|
<<"type">> => Type,
|
||||||
|
<<"name">> => Name,
|
||||||
<<"num_of_bridges">> => NumOfBridges
|
<<"num_of_bridges">> => NumOfBridges
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ start(Config) ->
|
||||||
case emqtt:connect(Pid) of
|
case emqtt:connect(Pid) of
|
||||||
{ok, _} ->
|
{ok, _} ->
|
||||||
try
|
try
|
||||||
ok = from_remote_topics(Pid, Subscriptions),
|
ok = sub_remote_topics(Pid, Subscriptions),
|
||||||
{ok, #{client_pid => Pid, subscriptions => Subscriptions}}
|
{ok, #{client_pid => Pid, subscriptions => Subscriptions}}
|
||||||
catch
|
catch
|
||||||
throw : Reason ->
|
throw : Reason ->
|
||||||
|
@ -171,7 +171,7 @@ handle_publish(Msg, Vars) ->
|
||||||
_ = erlang:apply(Mod, Func, [Msg | Args]);
|
_ = erlang:apply(Mod, Func, [Msg | Args]);
|
||||||
_ -> ok
|
_ -> ok
|
||||||
end,
|
end,
|
||||||
case maps:get(to_local_topic, Vars, undefined) of
|
case maps:get(local_topic, Vars, undefined) of
|
||||||
undefined -> ok;
|
undefined -> ok;
|
||||||
_Topic ->
|
_Topic ->
|
||||||
emqx_broker:publish(emqx_connector_mqtt_msg:to_broker_msg(Msg, Vars))
|
emqx_broker:publish(emqx_connector_mqtt_msg:to_broker_msg(Msg, Vars))
|
||||||
|
@ -186,8 +186,8 @@ make_hdlr(Parent, Vars) ->
|
||||||
disconnected => {fun ?MODULE:handle_disconnected/2, [Parent]}
|
disconnected => {fun ?MODULE:handle_disconnected/2, [Parent]}
|
||||||
}.
|
}.
|
||||||
|
|
||||||
from_remote_topics(_ClientPid, undefined) -> ok;
|
sub_remote_topics(_ClientPid, undefined) -> ok;
|
||||||
from_remote_topics(ClientPid, #{from_remote_topic := FromTopic, subscribe_qos := QoS}) ->
|
sub_remote_topics(ClientPid, #{remote_topic := FromTopic, remote_qos := QoS}) ->
|
||||||
case emqtt:subscribe(ClientPid, FromTopic, QoS) of
|
case emqtt:subscribe(ClientPid, FromTopic, QoS) of
|
||||||
{ok, _, _} -> ok;
|
{ok, _, _} -> ok;
|
||||||
Error -> throw(Error)
|
Error -> throw(Error)
|
||||||
|
|
|
@ -24,6 +24,10 @@
|
||||||
, estimate_size/1
|
, estimate_size/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-export([ replace_vars_in_str/2
|
||||||
|
, replace_simple_var/2
|
||||||
|
]).
|
||||||
|
|
||||||
-export_type([msg/0]).
|
-export_type([msg/0]).
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
@ -36,8 +40,8 @@
|
||||||
|
|
||||||
-type variables() :: #{
|
-type variables() :: #{
|
||||||
mountpoint := undefined | binary(),
|
mountpoint := undefined | binary(),
|
||||||
to_remote_topic := binary(),
|
remote_topic := binary(),
|
||||||
qos := original | integer(),
|
remote_qos := original | integer(),
|
||||||
retain := original | boolean(),
|
retain := original | boolean(),
|
||||||
payload := binary()
|
payload := binary()
|
||||||
}.
|
}.
|
||||||
|
@ -59,8 +63,8 @@ to_remote_msg(#message{flags = Flags0} = Msg, Vars) ->
|
||||||
Retain0 = maps:get(retain, Flags0, false),
|
Retain0 = maps:get(retain, Flags0, false),
|
||||||
MapMsg = maps:put(retain, Retain0, emqx_message:to_map(Msg)),
|
MapMsg = maps:put(retain, Retain0, emqx_message:to_map(Msg)),
|
||||||
to_remote_msg(MapMsg, Vars);
|
to_remote_msg(MapMsg, Vars);
|
||||||
to_remote_msg(MapMsg, #{to_remote_topic := TopicToken, payload := PayloadToken,
|
to_remote_msg(MapMsg, #{remote_topic := TopicToken, payload := PayloadToken,
|
||||||
qos := QoSToken, retain := RetainToken, mountpoint := Mountpoint}) when is_map(MapMsg) ->
|
remote_qos := QoSToken, retain := RetainToken, mountpoint := Mountpoint}) when is_map(MapMsg) ->
|
||||||
Topic = replace_vars_in_str(TopicToken, MapMsg),
|
Topic = replace_vars_in_str(TopicToken, MapMsg),
|
||||||
Payload = replace_vars_in_str(PayloadToken, MapMsg),
|
Payload = replace_vars_in_str(PayloadToken, MapMsg),
|
||||||
QoS = replace_simple_var(QoSToken, MapMsg),
|
QoS = replace_simple_var(QoSToken, MapMsg),
|
||||||
|
@ -75,8 +79,8 @@ to_remote_msg(#message{topic = Topic} = Msg, #{mountpoint := Mountpoint}) ->
|
||||||
|
|
||||||
%% published from remote node over a MQTT connection
|
%% published from remote node over a MQTT connection
|
||||||
to_broker_msg(#{dup := Dup, properties := Props} = MapMsg,
|
to_broker_msg(#{dup := Dup, properties := Props} = MapMsg,
|
||||||
#{to_local_topic := TopicToken, payload := PayloadToken,
|
#{local_topic := TopicToken, payload := PayloadToken,
|
||||||
qos := QoSToken, retain := RetainToken, mountpoint := Mountpoint}) ->
|
local_qos := QoSToken, retain := RetainToken, mountpoint := Mountpoint}) ->
|
||||||
Topic = replace_vars_in_str(TopicToken, MapMsg),
|
Topic = replace_vars_in_str(TopicToken, MapMsg),
|
||||||
Payload = replace_vars_in_str(PayloadToken, MapMsg),
|
Payload = replace_vars_in_str(PayloadToken, MapMsg),
|
||||||
QoS = replace_simple_var(QoSToken, MapMsg),
|
QoS = replace_simple_var(QoSToken, MapMsg),
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
%%
|
%%
|
||||||
%% Unless required by applicable law or agreed to in writing, software
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
%% cluster_shareload under the License is cluster_shareload on an "AS IS" BASIS,
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
%% See the License for the specific language governing permissions and
|
%% See the License for the specific language governing permissions and
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
|
@ -52,7 +52,7 @@ In 'cluster_shareload' mode, the incomming load from the remote broker is shared
|
||||||
using shared subscription.<br>
|
using shared subscription.<br>
|
||||||
Note that the 'clientid' is suffixed by the node name, this is to avoid
|
Note that the 'clientid' is suffixed by the node name, this is to avoid
|
||||||
clientid conflicts between different nodes. And we can only use shared subscription
|
clientid conflicts between different nodes. And we can only use shared subscription
|
||||||
topic filters for 'from_remote_topic'.
|
topic filters for 'remote_topic' of ingress connections.
|
||||||
"""
|
"""
|
||||||
})}
|
})}
|
||||||
, {server,
|
, {server,
|
||||||
|
@ -101,24 +101,31 @@ Queue messages in disk files.
|
||||||
] ++ emqx_connector_schema_lib:ssl_fields();
|
] ++ emqx_connector_schema_lib:ssl_fields();
|
||||||
|
|
||||||
fields("ingress") ->
|
fields("ingress") ->
|
||||||
%% the message maybe subscribed by rules, in this case 'to_local_topic' is not necessary
|
%% the message maybe subscribed by rules, in this case 'local_topic' is not necessary
|
||||||
[ {from_remote_topic,
|
[ {remote_topic,
|
||||||
sc(binary(),
|
sc(binary(),
|
||||||
#{ nullable => false
|
#{ nullable => false
|
||||||
, desc => "Receive messages from which topic of the remote broker"
|
, desc => "Receive messages from which topic of the remote broker"
|
||||||
})}
|
})}
|
||||||
, {subscribe_qos,
|
, {remote_qos,
|
||||||
sc(qos(),
|
sc(qos(),
|
||||||
#{ default => 1
|
#{ default => 1
|
||||||
, desc => "The QoS level to be used when subscribing to the remote broker"
|
, desc => "The QoS level to be used when subscribing to the remote broker"
|
||||||
})}
|
})}
|
||||||
, {to_local_topic,
|
, {local_topic,
|
||||||
sc(binary(),
|
sc(binary(),
|
||||||
#{ desc => """
|
#{ desc => """
|
||||||
Send messages to which topic of the local broker.<br>
|
Send messages to which topic of the local broker.<br>
|
||||||
Template with variables is allowed.
|
Template with variables is allowed.
|
||||||
"""
|
"""
|
||||||
})}
|
})}
|
||||||
|
, {local_qos,
|
||||||
|
sc(qos(),
|
||||||
|
#{ default => <<"${qos}">>
|
||||||
|
, desc => """
|
||||||
|
The QoS of the MQTT message to be sent.<br>
|
||||||
|
Template with variables is allowed."""
|
||||||
|
})}
|
||||||
, {hookpoint,
|
, {hookpoint,
|
||||||
sc(binary(),
|
sc(binary(),
|
||||||
#{ desc => """
|
#{ desc => """
|
||||||
|
@ -128,12 +135,12 @@ The hookpoint will be triggered when there's any message received from the remot
|
||||||
] ++ common_inout_confs();
|
] ++ common_inout_confs();
|
||||||
|
|
||||||
fields("egress") ->
|
fields("egress") ->
|
||||||
%% the message maybe sent from rules, in this case 'from_local_topic' is not necessary
|
%% the message maybe sent from rules, in this case 'local_topic' is not necessary
|
||||||
[ {from_local_topic,
|
[ {local_topic,
|
||||||
sc(binary(),
|
sc(binary(),
|
||||||
#{ desc => "The local topic to be forwarded to the remote broker"
|
#{ desc => "The local topic to be forwarded to the remote broker"
|
||||||
})}
|
})}
|
||||||
, {to_remote_topic,
|
, {remote_topic,
|
||||||
sc(binary(),
|
sc(binary(),
|
||||||
#{ default => <<"${topic}">>
|
#{ default => <<"${topic}">>
|
||||||
, desc => """
|
, desc => """
|
||||||
|
@ -141,6 +148,13 @@ Forward to which topic of the remote broker.<br>
|
||||||
Template with variables is allowed.
|
Template with variables is allowed.
|
||||||
"""
|
"""
|
||||||
})}
|
})}
|
||||||
|
, {remote_qos,
|
||||||
|
sc(qos(),
|
||||||
|
#{ default => <<"${qos}">>
|
||||||
|
, desc => """
|
||||||
|
The QoS of the MQTT message to be sent.<br>
|
||||||
|
Template with variables is allowed."""
|
||||||
|
})}
|
||||||
] ++ common_inout_confs();
|
] ++ common_inout_confs();
|
||||||
|
|
||||||
fields("replayq") ->
|
fields("replayq") ->
|
||||||
|
@ -187,31 +201,24 @@ topic_mappings() ->
|
||||||
ingress_desc() -> """
|
ingress_desc() -> """
|
||||||
The ingress config defines how this bridge receive messages from the remote MQTT broker, and then
|
The ingress config defines how this bridge receive messages from the remote MQTT broker, and then
|
||||||
send them to the local broker.<br>
|
send them to the local broker.<br>
|
||||||
Template with variables is allowed in 'to_local_topic', 'subscribe_qos', 'qos', 'retain',
|
Template with variables is allowed in 'local_topic', 'remote_qos', 'qos', 'retain',
|
||||||
'payload'.<br>
|
'payload'.<br>
|
||||||
NOTE: if this bridge is used as the input of a rule (emqx rule engine), and also to_local_topic is
|
NOTE: if this bridge is used as the input of a rule (emqx rule engine), and also local_topic is
|
||||||
configured, then messages got from the remote broker will be sent to both the 'to_local_topic' and
|
configured, then messages got from the remote broker will be sent to both the 'local_topic' and
|
||||||
the rule.
|
the rule.
|
||||||
""".
|
""".
|
||||||
|
|
||||||
egress_desc() -> """
|
egress_desc() -> """
|
||||||
The egress config defines how this bridge forwards messages from the local broker to the remote
|
The egress config defines how this bridge forwards messages from the local broker to the remote
|
||||||
broker.<br>
|
broker.<br>
|
||||||
Template with variables is allowed in 'to_remote_topic', 'qos', 'retain', 'payload'.<br>
|
Template with variables is allowed in 'remote_topic', 'qos', 'retain', 'payload'.<br>
|
||||||
NOTE: if this bridge is used as the output of a rule (emqx rule engine), and also from_local_topic
|
NOTE: if this bridge is used as the output of a rule (emqx rule engine), and also local_topic
|
||||||
is configured, then both the data got from the rule and the MQTT messages that matches
|
is configured, then both the data got from the rule and the MQTT messages that matches
|
||||||
from_local_topic will be forwarded.
|
local_topic will be forwarded.
|
||||||
""".
|
""".
|
||||||
|
|
||||||
common_inout_confs() ->
|
common_inout_confs() ->
|
||||||
[ {qos,
|
[ {retain,
|
||||||
sc(qos(),
|
|
||||||
#{ default => <<"${qos}">>
|
|
||||||
, desc => """
|
|
||||||
The QoS of the MQTT message to be sent.<br>
|
|
||||||
Template with variables is allowed."""
|
|
||||||
})}
|
|
||||||
, {retain,
|
|
||||||
sc(hoconsc:union([boolean(), binary()]),
|
sc(hoconsc:union([boolean(), binary()]),
|
||||||
#{ default => <<"${retain}">>
|
#{ default => <<"${retain}">>
|
||||||
, desc => """
|
, desc => """
|
||||||
|
|
|
@ -226,16 +226,22 @@ open_replayq(Name, QCfg) ->
|
||||||
marshaller => fun ?MODULE:msg_marshaller/1}).
|
marshaller => fun ?MODULE:msg_marshaller/1}).
|
||||||
|
|
||||||
pre_process_opts(#{subscriptions := InConf, forwards := OutConf} = ConnectOpts) ->
|
pre_process_opts(#{subscriptions := InConf, forwards := OutConf} = ConnectOpts) ->
|
||||||
ConnectOpts#{subscriptions => pre_process_in_out(InConf),
|
ConnectOpts#{subscriptions => pre_process_in_out(in, InConf),
|
||||||
forwards => pre_process_in_out(OutConf)}.
|
forwards => pre_process_in_out(out, OutConf)}.
|
||||||
|
|
||||||
pre_process_in_out(undefined) -> undefined;
|
pre_process_in_out(_, undefined) -> undefined;
|
||||||
pre_process_in_out(Conf) when is_map(Conf) ->
|
pre_process_in_out(in, Conf) when is_map(Conf) ->
|
||||||
Conf1 = pre_process_conf(to_local_topic, Conf),
|
Conf1 = pre_process_conf(local_topic, Conf),
|
||||||
Conf2 = pre_process_conf(to_remote_topic, Conf1),
|
Conf2 = pre_process_conf(local_qos, Conf1),
|
||||||
Conf3 = pre_process_conf(payload, Conf2),
|
pre_process_in_out_common(Conf2);
|
||||||
Conf4 = pre_process_conf(qos, Conf3),
|
pre_process_in_out(out, Conf) when is_map(Conf) ->
|
||||||
pre_process_conf(retain, Conf4).
|
Conf1 = pre_process_conf(remote_topic, Conf),
|
||||||
|
Conf2 = pre_process_conf(remote_qos, Conf1),
|
||||||
|
pre_process_in_out_common(Conf2).
|
||||||
|
|
||||||
|
pre_process_in_out_common(Conf) ->
|
||||||
|
Conf1 = pre_process_conf(payload, Conf),
|
||||||
|
pre_process_conf(retain, Conf1).
|
||||||
|
|
||||||
pre_process_conf(Key, Conf) ->
|
pre_process_conf(Key, Conf) ->
|
||||||
case maps:find(Key, Conf) of
|
case maps:find(Key, Conf) of
|
||||||
|
|
|
@ -46,11 +46,11 @@
|
||||||
#{
|
#{
|
||||||
<<"connector">> => ID,
|
<<"connector">> => ID,
|
||||||
<<"direction">> => <<"ingress">>,
|
<<"direction">> => <<"ingress">>,
|
||||||
<<"from_remote_topic">> => <<"remote_topic/#">>,
|
<<"remote_topic">> => <<"remote_topic/#">>,
|
||||||
<<"to_local_topic">> => <<"local_topic/${topic}">>,
|
<<"remote_qos">> => 2,
|
||||||
<<"subscribe_qos">> => 1,
|
<<"local_topic">> => <<"local_topic/${topic}">>,
|
||||||
|
<<"local_qos">> => <<"${qos}">>,
|
||||||
<<"payload">> => <<"${payload}">>,
|
<<"payload">> => <<"${payload}">>,
|
||||||
<<"qos">> => <<"${qos}">>,
|
|
||||||
<<"retain">> => <<"${retain}">>
|
<<"retain">> => <<"${retain}">>
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
@ -58,10 +58,10 @@
|
||||||
#{
|
#{
|
||||||
<<"connector">> => ID,
|
<<"connector">> => ID,
|
||||||
<<"direction">> => <<"egress">>,
|
<<"direction">> => <<"egress">>,
|
||||||
<<"from_local_topic">> => <<"local_topic/#">>,
|
<<"local_topic">> => <<"local_topic/#">>,
|
||||||
<<"to_remote_topic">> => <<"remote_topic/${topic}">>,
|
<<"remote_topic">> => <<"remote_topic/${topic}">>,
|
||||||
<<"payload">> => <<"${payload}">>,
|
<<"payload">> => <<"${payload}">>,
|
||||||
<<"qos">> => <<"${qos}">>,
|
<<"remote_qos">> => <<"${qos}">>,
|
||||||
<<"retain">> => <<"${retain}">>
|
<<"retain">> => <<"${retain}">>
|
||||||
}).
|
}).
|
||||||
|
|
||||||
|
@ -125,6 +125,8 @@ t_mqtt_crud_apis(_) ->
|
||||||
|
|
||||||
%ct:pal("---connector: ~p", [Connector]),
|
%ct:pal("---connector: ~p", [Connector]),
|
||||||
?assertMatch(#{ <<"id">> := ?CONNECTR_ID
|
?assertMatch(#{ <<"id">> := ?CONNECTR_ID
|
||||||
|
, <<"type">> := ?CONNECTR_TYPE
|
||||||
|
, <<"name">> := ?CONNECTR_NAME
|
||||||
, <<"server">> := <<"127.0.0.1:1883">>
|
, <<"server">> := <<"127.0.0.1:1883">>
|
||||||
, <<"username">> := User1
|
, <<"username">> := User1
|
||||||
, <<"password">> := <<"">>
|
, <<"password">> := <<"">>
|
||||||
|
@ -157,6 +159,8 @@ t_mqtt_crud_apis(_) ->
|
||||||
%% list all connectors again, assert Connector2 is in it
|
%% list all connectors again, assert Connector2 is in it
|
||||||
{ok, 200, Connector2Str} = request(get, uri(["connectors"]), []),
|
{ok, 200, Connector2Str} = request(get, uri(["connectors"]), []),
|
||||||
?assertMatch([#{ <<"id">> := ?CONNECTR_ID
|
?assertMatch([#{ <<"id">> := ?CONNECTR_ID
|
||||||
|
, <<"type">> := ?CONNECTR_TYPE
|
||||||
|
, <<"name">> := ?CONNECTR_NAME
|
||||||
, <<"server">> := <<"127.0.0.1:1883">>
|
, <<"server">> := <<"127.0.0.1:1883">>
|
||||||
, <<"username">> := User2
|
, <<"username">> := User2
|
||||||
, <<"password">> := <<"">>
|
, <<"password">> := <<"">>
|
||||||
|
@ -167,6 +171,8 @@ t_mqtt_crud_apis(_) ->
|
||||||
%% get the connector by id
|
%% get the connector by id
|
||||||
{ok, 200, Connector3Str} = request(get, uri(["connectors", ?CONNECTR_ID]), []),
|
{ok, 200, Connector3Str} = request(get, uri(["connectors", ?CONNECTR_ID]), []),
|
||||||
?assertMatch(#{ <<"id">> := ?CONNECTR_ID
|
?assertMatch(#{ <<"id">> := ?CONNECTR_ID
|
||||||
|
, <<"type">> := ?CONNECTR_TYPE
|
||||||
|
, <<"name">> := ?CONNECTR_NAME
|
||||||
, <<"server">> := <<"127.0.0.1:1883">>
|
, <<"server">> := <<"127.0.0.1:1883">>
|
||||||
, <<"username">> := User2
|
, <<"username">> := User2
|
||||||
, <<"password">> := <<"">>
|
, <<"password">> := <<"">>
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
|
|
||||||
-type rule() ::
|
-type rule() ::
|
||||||
#{ id := rule_id()
|
#{ id := rule_id()
|
||||||
|
, name := binary()
|
||||||
, sql := binary()
|
, sql := binary()
|
||||||
, outputs := [output()]
|
, outputs := [output()]
|
||||||
, enabled := boolean()
|
, enabled := boolean()
|
||||||
|
|
|
@ -38,14 +38,11 @@ roots() ->
|
||||||
].
|
].
|
||||||
|
|
||||||
fields("rule_creation") ->
|
fields("rule_creation") ->
|
||||||
[ {"id", sc(binary(),
|
emqx_rule_engine_schema:fields("rules");
|
||||||
#{ desc => "The Id of the rule", nullable => false
|
|
||||||
, example => "my_rule_id"
|
|
||||||
})}
|
|
||||||
] ++ emqx_rule_engine_schema:fields("rules");
|
|
||||||
|
|
||||||
fields("rule_info") ->
|
fields("rule_info") ->
|
||||||
[ {"metrics", sc(ref("metrics"), #{desc => "The metrics of the rule"})}
|
[ rule_id()
|
||||||
|
, {"metrics", sc(ref("metrics"), #{desc => "The metrics of the rule"})}
|
||||||
, {"node_metrics", sc(ref("node_metrics"), #{desc => "The metrics of the rule"})}
|
, {"node_metrics", sc(ref("node_metrics"), #{desc => "The metrics of the rule"})}
|
||||||
, {"from", sc(hoconsc:array(binary()),
|
, {"from", sc(hoconsc:array(binary()),
|
||||||
#{desc => "The topics of the rule", example => "t/#"})}
|
#{desc => "The topics of the rule", example => "t/#"})}
|
||||||
|
@ -182,5 +179,11 @@ qos() ->
|
||||||
{"qos", sc(hoconsc:union([typerefl:integer(0), typerefl:integer(1), typerefl:integer(2)]),
|
{"qos", sc(hoconsc:union([typerefl:integer(0), typerefl:integer(1), typerefl:integer(2)]),
|
||||||
#{desc => "The Message QoS"})}.
|
#{desc => "The Message QoS"})}.
|
||||||
|
|
||||||
|
rule_id() ->
|
||||||
|
{"id", sc(binary(),
|
||||||
|
#{ desc => "The Id of the rule", nullable => false
|
||||||
|
, example => "293fb66f"
|
||||||
|
})}.
|
||||||
|
|
||||||
sc(Type, Meta) -> hoconsc:mk(Type, Meta).
|
sc(Type, Meta) -> hoconsc:mk(Type, Meta).
|
||||||
ref(Field) -> hoconsc:ref(?MODULE, Field).
|
ref(Field) -> hoconsc:ref(?MODULE, Field).
|
||||||
|
|
|
@ -221,6 +221,7 @@ do_create_rule(Params = #{id := RuleId, sql := Sql, outputs := Outputs}) ->
|
||||||
{ok, Select} ->
|
{ok, Select} ->
|
||||||
Rule = #{
|
Rule = #{
|
||||||
id => RuleId,
|
id => RuleId,
|
||||||
|
name => maps:get(name, Params, <<"">>),
|
||||||
created_at => erlang:system_time(millisecond),
|
created_at => erlang:system_time(millisecond),
|
||||||
enabled => maps:get(enabled, Params, true),
|
enabled => maps:get(enabled, Params, true),
|
||||||
sql => Sql,
|
sql => Sql,
|
||||||
|
|
|
@ -59,9 +59,6 @@ error_schema(Code, Message) ->
|
||||||
rule_creation_schema() ->
|
rule_creation_schema() ->
|
||||||
ref(emqx_rule_api_schema, "rule_creation").
|
ref(emqx_rule_api_schema, "rule_creation").
|
||||||
|
|
||||||
rule_update_schema() ->
|
|
||||||
ref(emqx_rule_engine_schema, "rules").
|
|
||||||
|
|
||||||
rule_test_schema() ->
|
rule_test_schema() ->
|
||||||
ref(emqx_rule_api_schema, "rule_test").
|
ref(emqx_rule_api_schema, "rule_test").
|
||||||
|
|
||||||
|
@ -120,7 +117,7 @@ schema("/rules/:id") ->
|
||||||
description => <<"Update a rule by given Id to all nodes in the cluster">>,
|
description => <<"Update a rule by given Id to all nodes in the cluster">>,
|
||||||
summary => <<"Update a Rule">>,
|
summary => <<"Update a Rule">>,
|
||||||
parameters => param_path_id(),
|
parameters => param_path_id(),
|
||||||
requestBody => rule_update_schema(),
|
requestBody => rule_creation_schema(),
|
||||||
responses => #{
|
responses => #{
|
||||||
400 => error_schema('BAD_ARGS', "Invalid Parameters"),
|
400 => error_schema('BAD_ARGS', "Invalid Parameters"),
|
||||||
200 => rule_info_schema()
|
200 => rule_info_schema()
|
||||||
|
@ -167,7 +164,8 @@ param_path_id() ->
|
||||||
Records = emqx_rule_engine:get_rules_ordered_by_ts(),
|
Records = emqx_rule_engine:get_rules_ordered_by_ts(),
|
||||||
{200, format_rule_resp(Records)};
|
{200, format_rule_resp(Records)};
|
||||||
|
|
||||||
'/rules'(post, #{body := #{<<"id">> := Id} = Params}) ->
|
'/rules'(post, #{body := Params}) ->
|
||||||
|
Id = maps:get(<<"id">>, Params, list_to_binary(emqx_misc:gen_id(8))),
|
||||||
ConfPath = emqx_rule_engine:config_key_path() ++ [Id],
|
ConfPath = emqx_rule_engine:config_key_path() ++ [Id],
|
||||||
case emqx_rule_engine:get_rule(Id) of
|
case emqx_rule_engine:get_rule(Id) of
|
||||||
{ok, _Rule} ->
|
{ok, _Rule} ->
|
||||||
|
@ -230,7 +228,8 @@ err_msg(Msg) ->
|
||||||
format_rule_resp(Rules) when is_list(Rules) ->
|
format_rule_resp(Rules) when is_list(Rules) ->
|
||||||
[format_rule_resp(R) || R <- Rules];
|
[format_rule_resp(R) || R <- Rules];
|
||||||
|
|
||||||
format_rule_resp(#{ id := Id, created_at := CreatedAt,
|
format_rule_resp(#{ id := Id, name := Name,
|
||||||
|
created_at := CreatedAt,
|
||||||
from := Topics,
|
from := Topics,
|
||||||
outputs := Output,
|
outputs := Output,
|
||||||
sql := SQL,
|
sql := SQL,
|
||||||
|
@ -238,6 +237,7 @@ format_rule_resp(#{ id := Id, created_at := CreatedAt,
|
||||||
description := Descr}) ->
|
description := Descr}) ->
|
||||||
NodeMetrics = get_rule_metrics(Id),
|
NodeMetrics = get_rule_metrics(Id),
|
||||||
#{id => Id,
|
#{id => Id,
|
||||||
|
name => Name,
|
||||||
from => Topics,
|
from => Topics,
|
||||||
outputs => format_output(Output),
|
outputs => format_output(Output),
|
||||||
sql => SQL,
|
sql => SQL,
|
||||||
|
|
|
@ -39,7 +39,8 @@ fields("rule_engine") ->
|
||||||
];
|
];
|
||||||
|
|
||||||
fields("rules") ->
|
fields("rules") ->
|
||||||
[ {"sql", sc(binary(),
|
[ rule_name()
|
||||||
|
, {"sql", sc(binary(),
|
||||||
#{ desc => """
|
#{ desc => """
|
||||||
SQL query to transform the messages.<br>
|
SQL query to transform the messages.<br>
|
||||||
Example: <code>SELECT * FROM \"test/topic\" WHERE payload.x = 1</code><br>
|
Example: <code>SELECT * FROM \"test/topic\" WHERE payload.x = 1</code><br>
|
||||||
|
@ -177,6 +178,13 @@ of the rule, then the string \"undefined\" is used.
|
||||||
})}
|
})}
|
||||||
].
|
].
|
||||||
|
|
||||||
|
rule_name() ->
|
||||||
|
{"name", sc(binary(),
|
||||||
|
#{ desc => "The name of the rule"
|
||||||
|
, default => ""
|
||||||
|
, example => "foo"
|
||||||
|
})}.
|
||||||
|
|
||||||
outputs() ->
|
outputs() ->
|
||||||
[ binary()
|
[ binary()
|
||||||
, ref("builtin_output_republish")
|
, ref("builtin_output_republish")
|
||||||
|
|
Loading…
Reference in New Issue