feat: HTTP API for bridge_v2
This commit is contained in:
parent
a5a060473c
commit
38f5c7dcf4
|
@ -0,0 +1,582 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2023 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_v2_api).
|
||||||
|
|
||||||
|
-behaviour(minirest_api).
|
||||||
|
|
||||||
|
-include_lib("typerefl/include/types.hrl").
|
||||||
|
-include_lib("hocon/include/hoconsc.hrl").
|
||||||
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
-include_lib("emqx_utils/include/emqx_utils_api.hrl").
|
||||||
|
|
||||||
|
-import(hoconsc, [mk/2, array/1, enum/1]).
|
||||||
|
-import(emqx_utils, [redact/1]).
|
||||||
|
|
||||||
|
%% Swagger specs from hocon schema
|
||||||
|
-export([
|
||||||
|
api_spec/0,
|
||||||
|
paths/0,
|
||||||
|
schema/1,
|
||||||
|
namespace/0
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% API callbacks
|
||||||
|
-export([
|
||||||
|
'/bridges_v2'/2,
|
||||||
|
'/bridges_v2/:id'/2,
|
||||||
|
'/bridges_v2/:id/enable/:enable'/2
|
||||||
|
%%,
|
||||||
|
%% '/bridges_v2/:id/:operation'/2,
|
||||||
|
%% '/nodes/:node/bridges_v2/:id/:operation'/2,
|
||||||
|
%% '/bridges_v2_probe'/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% BpAPI
|
||||||
|
-export([lookup_from_local_node/2]).
|
||||||
|
|
||||||
|
-define(BRIDGE_NOT_FOUND(BRIDGE_TYPE, BRIDGE_NAME),
|
||||||
|
?NOT_FOUND(
|
||||||
|
<<"Bridge lookup failed: bridge named '", (bin(BRIDGE_NAME))/binary, "' of type ",
|
||||||
|
(bin(BRIDGE_TYPE))/binary, " does not exist.">>
|
||||||
|
)
|
||||||
|
).
|
||||||
|
|
||||||
|
-define(TRY_PARSE_ID(ID, EXPR),
|
||||||
|
try emqx_bridge_resource:parse_bridge_id(Id, #{atom_name => false}) of
|
||||||
|
{BridgeType, BridgeName} ->
|
||||||
|
EXPR
|
||||||
|
catch
|
||||||
|
throw:#{reason := Reason} ->
|
||||||
|
?NOT_FOUND(<<"Invalid bridge ID, ", Reason/binary>>)
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
|
namespace() -> "bridge_v2".
|
||||||
|
|
||||||
|
api_spec() ->
|
||||||
|
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => false}).
|
||||||
|
|
||||||
|
paths() ->
|
||||||
|
[
|
||||||
|
"/bridges_v2",
|
||||||
|
"/bridges_v2/:id",
|
||||||
|
"/bridges_v2/:id/enable/:enable"
|
||||||
|
%%,
|
||||||
|
%% "/bridges_v2/:id/:operation",
|
||||||
|
%% "/nodes/:node/bridges_v2/:id/:operation",
|
||||||
|
%% "/bridges_v2_probe"
|
||||||
|
].
|
||||||
|
|
||||||
|
error_schema(Code, Message) when is_atom(Code) ->
|
||||||
|
error_schema([Code], Message);
|
||||||
|
error_schema(Codes, Message) when is_list(Message) ->
|
||||||
|
error_schema(Codes, list_to_binary(Message));
|
||||||
|
error_schema(Codes, Message) when is_list(Codes) andalso is_binary(Message) ->
|
||||||
|
emqx_dashboard_swagger:error_codes(Codes, Message).
|
||||||
|
|
||||||
|
get_response_body_schema() ->
|
||||||
|
emqx_dashboard_swagger:schema_with_examples(
|
||||||
|
emqx_bridge_v2_schema:get_response(),
|
||||||
|
bridge_info_examples(get)
|
||||||
|
).
|
||||||
|
|
||||||
|
bridge_info_examples(Method) ->
|
||||||
|
maps:merge(
|
||||||
|
#{},
|
||||||
|
emqx_enterprise_bridge_examples(Method)
|
||||||
|
).
|
||||||
|
|
||||||
|
bridge_info_array_example(Method) ->
|
||||||
|
lists:map(fun(#{value := Config}) -> Config end, maps:values(bridge_info_examples(Method))).
|
||||||
|
|
||||||
|
-if(?EMQX_RELEASE_EDITION == ee).
|
||||||
|
emqx_enterprise_bridge_examples(Method) ->
|
||||||
|
emqx_bridge_v2_enterprise:examples(Method).
|
||||||
|
-else.
|
||||||
|
emqx_enterprise_bridgex_examples(_Method) -> #{}.
|
||||||
|
-endif.
|
||||||
|
|
||||||
|
param_path_id() ->
|
||||||
|
{id,
|
||||||
|
mk(
|
||||||
|
binary(),
|
||||||
|
#{
|
||||||
|
in => path,
|
||||||
|
required => true,
|
||||||
|
example => <<"webhook:webhook_example">>,
|
||||||
|
desc => ?DESC("desc_param_path_id")
|
||||||
|
}
|
||||||
|
)}.
|
||||||
|
|
||||||
|
param_path_enable() ->
|
||||||
|
{enable,
|
||||||
|
mk(
|
||||||
|
boolean(),
|
||||||
|
#{
|
||||||
|
in => path,
|
||||||
|
required => true,
|
||||||
|
desc => ?DESC("desc_param_path_enable"),
|
||||||
|
example => true
|
||||||
|
}
|
||||||
|
)}.
|
||||||
|
|
||||||
|
schema("/bridges_v2") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/bridges_v2',
|
||||||
|
get => #{
|
||||||
|
tags => [<<"bridges_v2">>],
|
||||||
|
summary => <<"List bridges">>,
|
||||||
|
description => ?DESC("desc_api1"),
|
||||||
|
responses => #{
|
||||||
|
200 => emqx_dashboard_swagger:schema_with_example(
|
||||||
|
array(emqx_bridge_v2_schema:get_response()),
|
||||||
|
bridge_info_array_example(get)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
post => #{
|
||||||
|
tags => [<<"bridges_v2">>],
|
||||||
|
summary => <<"Create bridge">>,
|
||||||
|
description => ?DESC("desc_api2"),
|
||||||
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
||||||
|
emqx_bridge_v2_schema:post_request(),
|
||||||
|
bridge_info_examples(post)
|
||||||
|
),
|
||||||
|
responses => #{
|
||||||
|
201 => get_response_body_schema(),
|
||||||
|
400 => error_schema('ALREADY_EXISTS', "Bridge already exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
schema("/bridges_v2/:id") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/bridges_v2/:id',
|
||||||
|
get => #{
|
||||||
|
tags => [<<"bridges_v2">>],
|
||||||
|
summary => <<"Get bridge">>,
|
||||||
|
description => ?DESC("desc_api3"),
|
||||||
|
parameters => [param_path_id()],
|
||||||
|
responses => #{
|
||||||
|
200 => get_response_body_schema(),
|
||||||
|
404 => error_schema('NOT_FOUND', "Bridge not found")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
put => #{
|
||||||
|
tags => [<<"bridges_v2">>],
|
||||||
|
summary => <<"Update bridge">>,
|
||||||
|
description => ?DESC("desc_api4"),
|
||||||
|
parameters => [param_path_id()],
|
||||||
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
||||||
|
emqx_bridge_v2_schema:put_request(),
|
||||||
|
bridge_info_examples(put)
|
||||||
|
),
|
||||||
|
responses => #{
|
||||||
|
200 => get_response_body_schema(),
|
||||||
|
404 => error_schema('NOT_FOUND', "Bridge not found"),
|
||||||
|
400 => error_schema('BAD_REQUEST', "Update bridge failed")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delete => #{
|
||||||
|
tags => [<<"bridges_v2">>],
|
||||||
|
summary => <<"Delete bridge">>,
|
||||||
|
description => ?DESC("desc_api5"),
|
||||||
|
parameters => [param_path_id()],
|
||||||
|
responses => #{
|
||||||
|
204 => <<"Bridge deleted">>,
|
||||||
|
400 => error_schema(
|
||||||
|
'BAD_REQUEST',
|
||||||
|
"Cannot delete bridge while active rules are defined for this bridge"
|
||||||
|
),
|
||||||
|
404 => error_schema('NOT_FOUND', "Bridge not found"),
|
||||||
|
503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
schema("/bridges_v2/:id/enable/:enable") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/bridges_v2/:id/enable/:enable',
|
||||||
|
put =>
|
||||||
|
#{
|
||||||
|
tags => [<<"bridges_v2">>],
|
||||||
|
summary => <<"Enable or disable bridge">>,
|
||||||
|
desc => ?DESC("desc_enable_bridge"),
|
||||||
|
parameters => [param_path_id(), param_path_enable()],
|
||||||
|
responses =>
|
||||||
|
#{
|
||||||
|
204 => <<"Success">>,
|
||||||
|
404 => error_schema(
|
||||||
|
'NOT_FOUND', "Bridge not found or invalid operation"
|
||||||
|
),
|
||||||
|
503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.
|
||||||
|
%% schema("/bridges/:id/:operation") ->
|
||||||
|
%% #{
|
||||||
|
%% 'operationId' => '/bridges/:id/:operation',
|
||||||
|
%% post => #{
|
||||||
|
%% tags => [<<"bridges">>],
|
||||||
|
%% summary => <<"Stop, start or restart bridge">>,
|
||||||
|
%% description => ?DESC("desc_api7"),
|
||||||
|
%% parameters => [
|
||||||
|
%% param_path_id(),
|
||||||
|
%% param_path_operation_cluster()
|
||||||
|
%% ],
|
||||||
|
%% responses => #{
|
||||||
|
%% 204 => <<"Operation success">>,
|
||||||
|
%% 400 => error_schema(
|
||||||
|
%% 'BAD_REQUEST', "Problem with configuration of external service"
|
||||||
|
%% ),
|
||||||
|
%% 404 => error_schema('NOT_FOUND', "Bridge not found or invalid operation"),
|
||||||
|
%% 501 => error_schema('NOT_IMPLEMENTED', "Not Implemented"),
|
||||||
|
%% 503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable")
|
||||||
|
%% }
|
||||||
|
%% }
|
||||||
|
%% };
|
||||||
|
%% schema("/nodes/:node/bridges/:id/:operation") ->
|
||||||
|
%% #{
|
||||||
|
%% 'operationId' => '/nodes/:node/bridges/:id/:operation',
|
||||||
|
%% post => #{
|
||||||
|
%% tags => [<<"bridges">>],
|
||||||
|
%% summary => <<"Stop, start or restart bridge">>,
|
||||||
|
%% description => ?DESC("desc_api8"),
|
||||||
|
%% parameters => [
|
||||||
|
%% param_path_node(),
|
||||||
|
%% param_path_id(),
|
||||||
|
%% param_path_operation_on_node()
|
||||||
|
%% ],
|
||||||
|
%% responses => #{
|
||||||
|
%% 204 => <<"Operation success">>,
|
||||||
|
%% 400 => error_schema(
|
||||||
|
%% 'BAD_REQUEST',
|
||||||
|
%% "Problem with configuration of external service or bridge not enabled"
|
||||||
|
%% ),
|
||||||
|
%% 404 => error_schema(
|
||||||
|
%% 'NOT_FOUND', "Bridge or node not found or invalid operation"
|
||||||
|
%% ),
|
||||||
|
%% 501 => error_schema('NOT_IMPLEMENTED', "Not Implemented"),
|
||||||
|
%% 503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable")
|
||||||
|
%% }
|
||||||
|
%% }
|
||||||
|
%% };
|
||||||
|
%% schema("/bridges_probe") ->
|
||||||
|
%% #{
|
||||||
|
%% 'operationId' => '/bridges_probe',
|
||||||
|
%% post => #{
|
||||||
|
%% tags => [<<"bridges">>],
|
||||||
|
%% desc => ?DESC("desc_api9"),
|
||||||
|
%% summary => <<"Test creating bridge">>,
|
||||||
|
%% 'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
||||||
|
%% emqx_bridge_schema:post_request(),
|
||||||
|
%% bridge_info_examples(post)
|
||||||
|
%% ),
|
||||||
|
%% responses => #{
|
||||||
|
%% 204 => <<"Test bridge OK">>,
|
||||||
|
%% 400 => error_schema(['TEST_FAILED'], "bridge test failed")
|
||||||
|
%% }
|
||||||
|
%% }
|
||||||
|
%% }.
|
||||||
|
|
||||||
|
'/bridges_v2'(post, #{body := #{<<"type">> := BridgeType, <<"name">> := BridgeName} = Conf0}) ->
|
||||||
|
case emqx_bridge_v2:lookup(BridgeType, BridgeName) of
|
||||||
|
{ok, _} ->
|
||||||
|
?BAD_REQUEST('ALREADY_EXISTS', <<"bridge already exists">>);
|
||||||
|
{error, bridge_not_found} ->
|
||||||
|
Conf = filter_out_request_body(Conf0),
|
||||||
|
create_bridge(BridgeType, BridgeName, Conf)
|
||||||
|
end;
|
||||||
|
'/bridges_v2'(get, _Params) ->
|
||||||
|
Nodes = mria:running_nodes(),
|
||||||
|
NodeReplies = emqx_bridge_proto_v5:v2_list_bridges_on_nodes(Nodes),
|
||||||
|
case is_ok(NodeReplies) of
|
||||||
|
{ok, NodeBridges} ->
|
||||||
|
AllBridges = [
|
||||||
|
[format_resource(Data, Node) || Data <- Bridges]
|
||||||
|
|| {Node, Bridges} <- lists:zip(Nodes, NodeBridges)
|
||||||
|
],
|
||||||
|
?OK(zip_bridges(AllBridges));
|
||||||
|
{error, Reason} ->
|
||||||
|
?INTERNAL_ERROR(Reason)
|
||||||
|
end;
|
||||||
|
'/bridges_v2'(post, _Params) ->
|
||||||
|
?BAD_REQUEST("Bad Request");
|
||||||
|
'/bridges_v2'(_, _) ->
|
||||||
|
?METHOD_NOT_ALLOWED.
|
||||||
|
|
||||||
|
'/bridges_v2/:id'(get, #{bindings := #{id := Id}}) ->
|
||||||
|
?TRY_PARSE_ID(Id, lookup_from_all_nodes(BridgeType, BridgeName, 200));
|
||||||
|
'/bridges_v2/:id'(put, #{bindings := #{id := Id}, body := Conf0}) ->
|
||||||
|
Conf1 = filter_out_request_body(Conf0),
|
||||||
|
?TRY_PARSE_ID(
|
||||||
|
Id,
|
||||||
|
case emqx_bridge_v2:lookup(BridgeType, BridgeName) of
|
||||||
|
{ok, _} ->
|
||||||
|
RawConf = emqx:get_raw_config([bridges, BridgeType, BridgeName], #{}),
|
||||||
|
Conf = deobfuscate(Conf1, RawConf),
|
||||||
|
update_bridge(BridgeType, BridgeName, Conf);
|
||||||
|
{error, bridge_not_found} ->
|
||||||
|
?BRIDGE_NOT_FOUND(BridgeType, BridgeName)
|
||||||
|
end
|
||||||
|
);
|
||||||
|
'/bridges_v2/:id'(delete, #{bindings := #{id := Id}}) ->
|
||||||
|
?TRY_PARSE_ID(
|
||||||
|
Id,
|
||||||
|
case emqx_bridge_v2:lookup(BridgeType, BridgeName) of
|
||||||
|
{ok, _} ->
|
||||||
|
case emqx_bridge_v2:remove(BridgeType, BridgeName) of
|
||||||
|
{ok, _} ->
|
||||||
|
?NO_CONTENT;
|
||||||
|
{error, {active_channels, Channels}} ->
|
||||||
|
?BAD_REQUEST(
|
||||||
|
{<<"Cannot delete bridge while there are active channels defined for this bridge">>,
|
||||||
|
Channels}
|
||||||
|
);
|
||||||
|
{error, timeout} ->
|
||||||
|
?SERVICE_UNAVAILABLE(<<"request timeout">>);
|
||||||
|
{error, Reason} ->
|
||||||
|
?INTERNAL_ERROR(Reason)
|
||||||
|
end;
|
||||||
|
{error, bridge_not_found} ->
|
||||||
|
?BRIDGE_NOT_FOUND(BridgeType, BridgeName)
|
||||||
|
end
|
||||||
|
);
|
||||||
|
'/bridges_v2/:id'(_, _) ->
|
||||||
|
?METHOD_NOT_ALLOWED.
|
||||||
|
|
||||||
|
'/bridges_v2/:id/enable/:enable'(put, #{bindings := #{id := Id, enable := Enable}}) ->
|
||||||
|
?TRY_PARSE_ID(
|
||||||
|
Id,
|
||||||
|
case enable_func(Enable) of
|
||||||
|
invalid ->
|
||||||
|
?NOT_FOUND(<<"Invalid operation">>);
|
||||||
|
OperFunc ->
|
||||||
|
case emqx_bridge_v2:disable_enable(OperFunc, BridgeType, BridgeName) of
|
||||||
|
{ok, _} ->
|
||||||
|
?NO_CONTENT;
|
||||||
|
{error, {pre_config_update, _, bridge_not_found}} ->
|
||||||
|
?BRIDGE_NOT_FOUND(BridgeType, BridgeName);
|
||||||
|
{error, {_, _, timeout}} ->
|
||||||
|
?SERVICE_UNAVAILABLE(<<"request timeout">>);
|
||||||
|
{error, timeout} ->
|
||||||
|
?SERVICE_UNAVAILABLE(<<"request timeout">>);
|
||||||
|
{error, Reason} ->
|
||||||
|
?INTERNAL_ERROR(Reason)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
);
|
||||||
|
'/bridges_v2/:id/enable/:enable'(_, _) ->
|
||||||
|
?METHOD_NOT_ALLOWED.
|
||||||
|
|
||||||
|
%%% API helpers
|
||||||
|
is_ok(ok) ->
|
||||||
|
ok;
|
||||||
|
is_ok(OkResult = {ok, _}) ->
|
||||||
|
OkResult;
|
||||||
|
is_ok(Error = {error, _}) ->
|
||||||
|
Error;
|
||||||
|
is_ok(ResL) ->
|
||||||
|
case
|
||||||
|
lists:filter(
|
||||||
|
fun
|
||||||
|
({ok, _}) -> false;
|
||||||
|
(ok) -> false;
|
||||||
|
(_) -> true
|
||||||
|
end,
|
||||||
|
ResL
|
||||||
|
)
|
||||||
|
of
|
||||||
|
[] -> {ok, [Res || {ok, Res} <- ResL]};
|
||||||
|
ErrL -> hd(ErrL)
|
||||||
|
end.
|
||||||
|
|
||||||
|
deobfuscate(NewConf, OldConf) ->
|
||||||
|
maps:fold(
|
||||||
|
fun(K, V, Acc) ->
|
||||||
|
case maps:find(K, OldConf) of
|
||||||
|
error ->
|
||||||
|
Acc#{K => V};
|
||||||
|
{ok, OldV} when is_map(V), is_map(OldV) ->
|
||||||
|
Acc#{K => deobfuscate(V, OldV)};
|
||||||
|
{ok, OldV} ->
|
||||||
|
case emqx_utils:is_redacted(K, V) of
|
||||||
|
true ->
|
||||||
|
Acc#{K => OldV};
|
||||||
|
_ ->
|
||||||
|
Acc#{K => V}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
#{},
|
||||||
|
NewConf
|
||||||
|
).
|
||||||
|
|
||||||
|
%% bridge helpers
|
||||||
|
lookup_from_all_nodes(BridgeType, BridgeName, SuccCode) ->
|
||||||
|
Nodes = mria:running_nodes(),
|
||||||
|
case is_ok(emqx_bridge_proto_v5:v2_lookup_from_all_nodes(Nodes, BridgeType, BridgeName)) of
|
||||||
|
{ok, [{ok, _} | _] = Results} ->
|
||||||
|
{SuccCode, format_bridge_info([R || {ok, R} <- Results])};
|
||||||
|
{ok, [{error, bridge_not_found} | _]} ->
|
||||||
|
?BRIDGE_NOT_FOUND(BridgeType, BridgeName);
|
||||||
|
{error, Reason} ->
|
||||||
|
?INTERNAL_ERROR(Reason)
|
||||||
|
end.
|
||||||
|
|
||||||
|
zip_bridges([BridgesFirstNode | _] = BridgesAllNodes) ->
|
||||||
|
lists:foldl(
|
||||||
|
fun(#{type := Type, name := Name}, Acc) ->
|
||||||
|
Bridges = pick_bridges_by_id(Type, Name, BridgesAllNodes),
|
||||||
|
[format_bridge_info(Bridges) | Acc]
|
||||||
|
end,
|
||||||
|
[],
|
||||||
|
BridgesFirstNode
|
||||||
|
).
|
||||||
|
|
||||||
|
pick_bridges_by_id(Type, Name, BridgesAllNodes) ->
|
||||||
|
lists:foldl(
|
||||||
|
fun(BridgesOneNode, Acc) ->
|
||||||
|
case
|
||||||
|
[
|
||||||
|
Bridge
|
||||||
|
|| Bridge = #{type := Type0, name := Name0} <- BridgesOneNode,
|
||||||
|
Type0 == Type,
|
||||||
|
Name0 == Name
|
||||||
|
]
|
||||||
|
of
|
||||||
|
[BridgeInfo] ->
|
||||||
|
[BridgeInfo | Acc];
|
||||||
|
[] ->
|
||||||
|
?SLOG(warning, #{
|
||||||
|
msg => "bridge_inconsistent_in_cluster",
|
||||||
|
reason => not_found,
|
||||||
|
type => Type,
|
||||||
|
name => Name,
|
||||||
|
bridge => emqx_bridge_resource:bridge_id(Type, Name)
|
||||||
|
}),
|
||||||
|
Acc
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
[],
|
||||||
|
BridgesAllNodes
|
||||||
|
).
|
||||||
|
|
||||||
|
format_bridge_info([FirstBridge | _] = Bridges) ->
|
||||||
|
Res = maps:remove(node, FirstBridge),
|
||||||
|
NodeStatus = node_status(Bridges),
|
||||||
|
redact(Res#{
|
||||||
|
status => aggregate_status(NodeStatus),
|
||||||
|
node_status => NodeStatus
|
||||||
|
}).
|
||||||
|
|
||||||
|
node_status(Bridges) ->
|
||||||
|
[maps:with([node, status, status_reason], B) || B <- Bridges].
|
||||||
|
|
||||||
|
aggregate_status(AllStatus) ->
|
||||||
|
Head = fun([A | _]) -> A end,
|
||||||
|
HeadVal = maps:get(status, Head(AllStatus), connecting),
|
||||||
|
AllRes = lists:all(fun(#{status := Val}) -> Val == HeadVal end, AllStatus),
|
||||||
|
case AllRes of
|
||||||
|
true -> HeadVal;
|
||||||
|
false -> inconsistent
|
||||||
|
end.
|
||||||
|
|
||||||
|
lookup_from_local_node(BridgeType, BridgeName) ->
|
||||||
|
case emqx_bridge_v2:lookup(BridgeType, BridgeName) of
|
||||||
|
{ok, Res} -> {ok, format_resource(Res, node())};
|
||||||
|
Error -> Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% resource
|
||||||
|
format_resource(
|
||||||
|
#{
|
||||||
|
type := Type,
|
||||||
|
name := Name,
|
||||||
|
raw_config := RawConf,
|
||||||
|
resource_data := ResourceData
|
||||||
|
},
|
||||||
|
Node
|
||||||
|
) ->
|
||||||
|
redact(
|
||||||
|
maps:merge(
|
||||||
|
RawConf#{
|
||||||
|
type => Type,
|
||||||
|
name => maps:get(<<"name">>, RawConf, Name),
|
||||||
|
node => Node
|
||||||
|
},
|
||||||
|
format_resource_data(ResourceData)
|
||||||
|
)
|
||||||
|
).
|
||||||
|
|
||||||
|
format_resource_data(ResData) ->
|
||||||
|
maps:fold(fun format_resource_data/3, #{}, maps:with([status, error], ResData)).
|
||||||
|
|
||||||
|
format_resource_data(error, undefined, Result) ->
|
||||||
|
Result;
|
||||||
|
format_resource_data(error, Error, Result) ->
|
||||||
|
Result#{status_reason => emqx_utils:readable_error_msg(Error)};
|
||||||
|
format_resource_data(K, V, Result) ->
|
||||||
|
Result#{K => V}.
|
||||||
|
|
||||||
|
create_bridge(BridgeType, BridgeName, Conf) ->
|
||||||
|
create_or_update_bridge(BridgeType, BridgeName, Conf, 201).
|
||||||
|
|
||||||
|
update_bridge(BridgeType, BridgeName, Conf) ->
|
||||||
|
create_or_update_bridge(BridgeType, BridgeName, Conf, 200).
|
||||||
|
|
||||||
|
create_or_update_bridge(BridgeType, BridgeName, Conf, HttpStatusCode) ->
|
||||||
|
case emqx_bridge_v2:create(BridgeType, BridgeName, Conf) of
|
||||||
|
{ok, _} ->
|
||||||
|
lookup_from_all_nodes(BridgeType, BridgeName, HttpStatusCode);
|
||||||
|
{error, Reason} when is_map(Reason) ->
|
||||||
|
?BAD_REQUEST(map_to_json(redact(Reason)))
|
||||||
|
end.
|
||||||
|
|
||||||
|
enable_func(<<"true">>) -> enable;
|
||||||
|
enable_func(<<"false">>) -> disable;
|
||||||
|
enable_func(_) -> invalid.
|
||||||
|
|
||||||
|
filter_out_request_body(Conf) ->
|
||||||
|
ExtraConfs = [
|
||||||
|
<<"id">>,
|
||||||
|
<<"type">>,
|
||||||
|
<<"name">>,
|
||||||
|
<<"status">>,
|
||||||
|
<<"status_reason">>,
|
||||||
|
<<"node_status">>,
|
||||||
|
<<"node">>
|
||||||
|
],
|
||||||
|
maps:without(ExtraConfs, Conf).
|
||||||
|
|
||||||
|
%% general helpers
|
||||||
|
bin(S) when is_list(S) ->
|
||||||
|
list_to_binary(S);
|
||||||
|
bin(S) when is_atom(S) ->
|
||||||
|
atom_to_binary(S, utf8);
|
||||||
|
bin(S) when is_binary(S) ->
|
||||||
|
S.
|
||||||
|
|
||||||
|
map_to_json(M0) ->
|
||||||
|
%% When dealing with Hocon validation errors, `value' might contain non-serializable
|
||||||
|
%% values (e.g.: user_lookup_fun), so we try again without that key if serialization
|
||||||
|
%% fails as a best effort.
|
||||||
|
M1 = emqx_utils_maps:jsonable_map(M0, fun(K, V) -> {K, emqx_utils_maps:binary_string(V)} end),
|
||||||
|
try
|
||||||
|
emqx_utils_json:encode(M1)
|
||||||
|
catch
|
||||||
|
error:_ ->
|
||||||
|
M2 = maps:without([value, <<"value">>], M1),
|
||||||
|
emqx_utils_json:encode(M2)
|
||||||
|
end.
|
|
@ -0,0 +1,154 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2022-2023 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_proto_v5).
|
||||||
|
|
||||||
|
-behaviour(emqx_bpapi).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
introduced_in/0,
|
||||||
|
|
||||||
|
list_bridges_on_nodes/1,
|
||||||
|
restart_bridge_to_node/3,
|
||||||
|
start_bridge_to_node/3,
|
||||||
|
stop_bridge_to_node/3,
|
||||||
|
lookup_from_all_nodes/3,
|
||||||
|
get_metrics_from_all_nodes/3,
|
||||||
|
restart_bridges_to_all_nodes/3,
|
||||||
|
start_bridges_to_all_nodes/3,
|
||||||
|
stop_bridges_to_all_nodes/3,
|
||||||
|
|
||||||
|
v2_list_bridges_on_nodes/1,
|
||||||
|
v2_lookup_from_all_nodes/3
|
||||||
|
]).
|
||||||
|
|
||||||
|
-include_lib("emqx/include/bpapi.hrl").
|
||||||
|
|
||||||
|
-define(TIMEOUT, 15000).
|
||||||
|
|
||||||
|
introduced_in() ->
|
||||||
|
"5.3.1".
|
||||||
|
|
||||||
|
-spec list_bridges_on_nodes([node()]) ->
|
||||||
|
emqx_rpc:erpc_multicall([emqx_resource:resource_data()]).
|
||||||
|
list_bridges_on_nodes(Nodes) ->
|
||||||
|
erpc:multicall(Nodes, emqx_bridge, list, [], ?TIMEOUT).
|
||||||
|
|
||||||
|
-type key() :: atom() | binary() | [byte()].
|
||||||
|
|
||||||
|
-spec restart_bridge_to_node(node(), key(), key()) ->
|
||||||
|
term().
|
||||||
|
restart_bridge_to_node(Node, BridgeType, BridgeName) ->
|
||||||
|
rpc:call(
|
||||||
|
Node,
|
||||||
|
emqx_bridge_resource,
|
||||||
|
restart,
|
||||||
|
[BridgeType, BridgeName],
|
||||||
|
?TIMEOUT
|
||||||
|
).
|
||||||
|
|
||||||
|
-spec start_bridge_to_node(node(), key(), key()) ->
|
||||||
|
term().
|
||||||
|
start_bridge_to_node(Node, BridgeType, BridgeName) ->
|
||||||
|
rpc:call(
|
||||||
|
Node,
|
||||||
|
emqx_bridge_resource,
|
||||||
|
start,
|
||||||
|
[BridgeType, BridgeName],
|
||||||
|
?TIMEOUT
|
||||||
|
).
|
||||||
|
|
||||||
|
-spec stop_bridge_to_node(node(), key(), key()) ->
|
||||||
|
term().
|
||||||
|
stop_bridge_to_node(Node, BridgeType, BridgeName) ->
|
||||||
|
rpc:call(
|
||||||
|
Node,
|
||||||
|
emqx_bridge_resource,
|
||||||
|
stop,
|
||||||
|
[BridgeType, BridgeName],
|
||||||
|
?TIMEOUT
|
||||||
|
).
|
||||||
|
|
||||||
|
-spec restart_bridges_to_all_nodes([node()], key(), key()) ->
|
||||||
|
emqx_rpc:erpc_multicall().
|
||||||
|
restart_bridges_to_all_nodes(Nodes, BridgeType, BridgeName) ->
|
||||||
|
erpc:multicall(
|
||||||
|
Nodes,
|
||||||
|
emqx_bridge_resource,
|
||||||
|
restart,
|
||||||
|
[BridgeType, BridgeName],
|
||||||
|
?TIMEOUT
|
||||||
|
).
|
||||||
|
|
||||||
|
-spec start_bridges_to_all_nodes([node()], key(), key()) ->
|
||||||
|
emqx_rpc:erpc_multicall().
|
||||||
|
start_bridges_to_all_nodes(Nodes, BridgeType, BridgeName) ->
|
||||||
|
erpc:multicall(
|
||||||
|
Nodes,
|
||||||
|
emqx_bridge_resource,
|
||||||
|
start,
|
||||||
|
[BridgeType, BridgeName],
|
||||||
|
?TIMEOUT
|
||||||
|
).
|
||||||
|
|
||||||
|
-spec stop_bridges_to_all_nodes([node()], key(), key()) ->
|
||||||
|
emqx_rpc:erpc_multicall().
|
||||||
|
stop_bridges_to_all_nodes(Nodes, BridgeType, BridgeName) ->
|
||||||
|
erpc:multicall(
|
||||||
|
Nodes,
|
||||||
|
emqx_bridge_resource,
|
||||||
|
stop,
|
||||||
|
[BridgeType, BridgeName],
|
||||||
|
?TIMEOUT
|
||||||
|
).
|
||||||
|
|
||||||
|
-spec lookup_from_all_nodes([node()], key(), key()) ->
|
||||||
|
emqx_rpc:erpc_multicall().
|
||||||
|
lookup_from_all_nodes(Nodes, BridgeType, BridgeName) ->
|
||||||
|
erpc:multicall(
|
||||||
|
Nodes,
|
||||||
|
emqx_bridge_api,
|
||||||
|
lookup_from_local_node,
|
||||||
|
[BridgeType, BridgeName],
|
||||||
|
?TIMEOUT
|
||||||
|
).
|
||||||
|
|
||||||
|
-spec get_metrics_from_all_nodes([node()], key(), key()) ->
|
||||||
|
emqx_rpc:erpc_multicall(emqx_metrics_worker:metrics()).
|
||||||
|
get_metrics_from_all_nodes(Nodes, BridgeType, BridgeName) ->
|
||||||
|
erpc:multicall(
|
||||||
|
Nodes,
|
||||||
|
emqx_bridge_api,
|
||||||
|
get_metrics_from_local_node,
|
||||||
|
[BridgeType, BridgeName],
|
||||||
|
?TIMEOUT
|
||||||
|
).
|
||||||
|
|
||||||
|
-spec v2_list_bridges_on_nodes([node()]) ->
|
||||||
|
emqx_rpc:erpc_multicall([emqx_resource:resource_data()]).
|
||||||
|
v2_list_bridges_on_nodes(Nodes) ->
|
||||||
|
erpc:multicall(Nodes, emqx_bridge_v2, list, [], ?TIMEOUT).
|
||||||
|
|
||||||
|
-spec v2_lookup_from_all_nodes([node()], key(), key()) ->
|
||||||
|
emqx_rpc:erpc_multicall().
|
||||||
|
v2_lookup_from_all_nodes(Nodes, BridgeType, BridgeName) ->
|
||||||
|
erpc:multicall(
|
||||||
|
Nodes,
|
||||||
|
emqx_bridge_v2_api,
|
||||||
|
lookup_from_local_node,
|
||||||
|
[BridgeType, BridgeName],
|
||||||
|
?TIMEOUT
|
||||||
|
).
|
|
@ -5,13 +5,31 @@
|
||||||
|
|
||||||
-if(?EMQX_RELEASE_EDITION == ee).
|
-if(?EMQX_RELEASE_EDITION == ee).
|
||||||
|
|
||||||
-include_lib("hocon/include/hoconsc.hrl").
|
|
||||||
-import(hoconsc, [mk/2, enum/1, ref/2]).
|
-import(hoconsc, [mk/2, enum/1, ref/2]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
|
api_schemas/1,
|
||||||
|
examples/1,
|
||||||
fields/1
|
fields/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
examples(Method) ->
|
||||||
|
MergeFun =
|
||||||
|
fun(Example, Examples) ->
|
||||||
|
maps:merge(Examples, Example)
|
||||||
|
end,
|
||||||
|
Fun =
|
||||||
|
fun(Module, Examples) ->
|
||||||
|
ConnectorExamples = erlang:apply(Module, bridge_v2_examples, [Method]),
|
||||||
|
lists:foldl(MergeFun, Examples, ConnectorExamples)
|
||||||
|
end,
|
||||||
|
lists:foldl(Fun, #{}, schema_modules()).
|
||||||
|
|
||||||
|
schema_modules() ->
|
||||||
|
[
|
||||||
|
emqx_bridge_kafka
|
||||||
|
].
|
||||||
|
|
||||||
fields(bridges_v2) ->
|
fields(bridges_v2) ->
|
||||||
kafka_structs().
|
kafka_structs().
|
||||||
|
|
||||||
|
@ -27,6 +45,18 @@ kafka_structs() ->
|
||||||
)}
|
)}
|
||||||
].
|
].
|
||||||
|
|
||||||
|
api_schemas(Method) ->
|
||||||
|
[
|
||||||
|
%% We need to map the `type' field of a request (binary) to a
|
||||||
|
%% connector schema module.
|
||||||
|
%% TODO: rename this to `kafka_producer' after alias support is added
|
||||||
|
%% to hocon; keeping this as just `kafka' for backwards compatibility.
|
||||||
|
api_ref(emqx_bridge_kafka, <<"kafka">>, Method ++ "_bridge_v2")
|
||||||
|
].
|
||||||
|
|
||||||
|
api_ref(Module, Type, Method) ->
|
||||||
|
{Type, ref(Module, Method)}.
|
||||||
|
|
||||||
-else.
|
-else.
|
||||||
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|
|
@ -23,7 +23,21 @@
|
||||||
|
|
||||||
-export([roots/0, fields/1, desc/1, namespace/0, tags/0]).
|
-export([roots/0, fields/1, desc/1, namespace/0, tags/0]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
get_response/0,
|
||||||
|
put_request/0,
|
||||||
|
post_request/0
|
||||||
|
]).
|
||||||
|
|
||||||
-if(?EMQX_RELEASE_EDITION == ee).
|
-if(?EMQX_RELEASE_EDITION == ee).
|
||||||
|
enterprise_api_schemas(Method) ->
|
||||||
|
%% We *must* do this to ensure the module is really loaded, especially when we use
|
||||||
|
%% `call_hocon' from `nodetool' to generate initial configurations.
|
||||||
|
_ = emqx_bridge_v2_enterprise:module_info(),
|
||||||
|
case erlang:function_exported(emqx_bridge_v2_enterprise, api_schemas, 1) of
|
||||||
|
true -> emqx_bridge_v2_enterprise:api_schemas(Method);
|
||||||
|
false -> []
|
||||||
|
end.
|
||||||
|
|
||||||
enterprise_fields_actions() ->
|
enterprise_fields_actions() ->
|
||||||
%% We *must* do this to ensure the module is really loaded, especially when we use
|
%% We *must* do this to ensure the module is really loaded, especially when we use
|
||||||
|
@ -42,11 +56,58 @@ enterprise_fields_actions() -> [].
|
||||||
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
|
%%======================================================================================
|
||||||
|
%% For HTTP APIs
|
||||||
|
get_response() ->
|
||||||
|
api_schema("get").
|
||||||
|
|
||||||
|
put_request() ->
|
||||||
|
api_schema("put").
|
||||||
|
|
||||||
|
post_request() ->
|
||||||
|
api_schema("post").
|
||||||
|
|
||||||
|
api_schema(Method) ->
|
||||||
|
Broker = [
|
||||||
|
{Type, ref(Mod, Method)}
|
||||||
|
|| {Type, Mod} <- [
|
||||||
|
% {<<"webhook">>, emqx_bridge_http_schema},
|
||||||
|
% {<<"mqtt">>, emqx_bridge_mqtt_schema}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
EE = enterprise_api_schemas(Method),
|
||||||
|
hoconsc:union(bridge_api_union(Broker ++ EE)).
|
||||||
|
|
||||||
|
bridge_api_union(Refs) ->
|
||||||
|
Index = maps:from_list(Refs),
|
||||||
|
fun
|
||||||
|
(all_union_members) ->
|
||||||
|
maps:values(Index);
|
||||||
|
({value, V}) ->
|
||||||
|
case V of
|
||||||
|
#{<<"type">> := T} ->
|
||||||
|
case maps:get(T, Index, undefined) of
|
||||||
|
undefined ->
|
||||||
|
throw(#{
|
||||||
|
field_name => type,
|
||||||
|
reason => <<"unknown bridge type">>
|
||||||
|
});
|
||||||
|
Ref ->
|
||||||
|
[Ref]
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
throw(#{
|
||||||
|
field_name => type,
|
||||||
|
reason => <<"unknown bridge type">>
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
%%======================================================================================
|
%%======================================================================================
|
||||||
%% HOCON Schema Callbacks
|
%% HOCON Schema Callbacks
|
||||||
%%======================================================================================
|
%%======================================================================================
|
||||||
|
|
||||||
namespace() -> "bridge_v2".
|
namespace() -> "bridges_v2".
|
||||||
|
|
||||||
tags() ->
|
tags() ->
|
||||||
[<<"Bridge V2">>].
|
[<<"Bridge V2">>].
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
-module(emqx_bridge_kafka).
|
-module(emqx_bridge_kafka).
|
||||||
|
|
||||||
-include_lib("emqx_connector/include/emqx_connector.hrl").
|
|
||||||
-include_lib("typerefl/include/types.hrl").
|
-include_lib("typerefl/include/types.hrl").
|
||||||
-include_lib("hocon/include/hoconsc.hrl").
|
-include_lib("hocon/include/hoconsc.hrl").
|
||||||
|
|
||||||
|
@ -18,6 +17,7 @@
|
||||||
-import(hoconsc, [mk/2, enum/1, ref/2]).
|
-import(hoconsc, [mk/2, enum/1, ref/2]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
|
bridge_v2_examples/1,
|
||||||
conn_bridge_examples/1,
|
conn_bridge_examples/1,
|
||||||
connector_examples/1
|
connector_examples/1
|
||||||
]).
|
]).
|
||||||
|
@ -47,6 +47,16 @@ connector_examples(_Method) ->
|
||||||
}
|
}
|
||||||
].
|
].
|
||||||
|
|
||||||
|
bridge_v2_examples(Method) ->
|
||||||
|
[
|
||||||
|
#{
|
||||||
|
<<"kafka">> => #{
|
||||||
|
summary => <<"Kafka Bridge v2">>,
|
||||||
|
value => values({Method, bridge_v2_producer})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
].
|
||||||
|
|
||||||
conn_bridge_examples(Method) ->
|
conn_bridge_examples(Method) ->
|
||||||
[
|
[
|
||||||
#{
|
#{
|
||||||
|
@ -67,11 +77,41 @@ conn_bridge_examples(Method) ->
|
||||||
].
|
].
|
||||||
|
|
||||||
values({get, KafkaType}) ->
|
values({get, KafkaType}) ->
|
||||||
values({post, KafkaType});
|
maps:merge(
|
||||||
|
#{
|
||||||
|
status => <<"connected">>,
|
||||||
|
node_status => [
|
||||||
|
#{
|
||||||
|
node => <<"emqx@localhost">>,
|
||||||
|
status => <<"connected">>
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
values({post, KafkaType})
|
||||||
|
);
|
||||||
values({post, KafkaType}) ->
|
values({post, KafkaType}) ->
|
||||||
maps:merge(values(common_config), values(KafkaType));
|
maps:merge(
|
||||||
|
#{
|
||||||
|
name => <<"my_bridge">>,
|
||||||
|
type => <<"kafka">>
|
||||||
|
},
|
||||||
|
values({put, KafkaType})
|
||||||
|
);
|
||||||
|
values({put, KafkaType}) when KafkaType =:= bridge_v2_producer ->
|
||||||
|
values(KafkaType);
|
||||||
values({put, KafkaType}) ->
|
values({put, KafkaType}) ->
|
||||||
values({post, KafkaType});
|
maps:merge(values(common_config), values(KafkaType));
|
||||||
|
values(bridge_v2_producer) ->
|
||||||
|
maps:merge(
|
||||||
|
#{
|
||||||
|
enable => true,
|
||||||
|
connector => <<"my_connector">>,
|
||||||
|
resource_opts => #{
|
||||||
|
health_check_interval => "32s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
values(producer)
|
||||||
|
);
|
||||||
values(common_config) ->
|
values(common_config) ->
|
||||||
#{
|
#{
|
||||||
authentication => #{
|
authentication => #{
|
||||||
|
@ -160,7 +200,7 @@ host_opts() ->
|
||||||
|
|
||||||
namespace() -> "bridge_kafka".
|
namespace() -> "bridge_kafka".
|
||||||
|
|
||||||
roots() -> ["config_consumer", "config_producer"].
|
roots() -> ["config_consumer", "config_producer", "config_bridge_v2"].
|
||||||
|
|
||||||
fields("post_" ++ Type) ->
|
fields("post_" ++ Type) ->
|
||||||
[type_field(Type), name_field() | fields("config_" ++ Type)];
|
[type_field(Type), name_field() | fields("config_" ++ Type)];
|
||||||
|
@ -168,6 +208,8 @@ fields("put_" ++ Type) ->
|
||||||
fields("config_" ++ Type);
|
fields("config_" ++ Type);
|
||||||
fields("get_" ++ Type) ->
|
fields("get_" ++ Type) ->
|
||||||
emqx_bridge_schema:status_fields() ++ fields("post_" ++ Type);
|
emqx_bridge_schema:status_fields() ++ fields("post_" ++ Type);
|
||||||
|
fields("config_bridge_v2") ->
|
||||||
|
fields(kafka_producer_action);
|
||||||
fields("config_connector") ->
|
fields("config_connector") ->
|
||||||
fields(kafka_connector);
|
fields(kafka_connector);
|
||||||
fields("config_producer") ->
|
fields("config_producer") ->
|
||||||
|
|
|
@ -347,7 +347,11 @@ schema("/connectors_probe") ->
|
||||||
?OK(zip_connectors(AllConnectors));
|
?OK(zip_connectors(AllConnectors));
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?INTERNAL_ERROR(Reason)
|
?INTERNAL_ERROR(Reason)
|
||||||
end.
|
end;
|
||||||
|
'/connectors'(post, _Params) ->
|
||||||
|
?BAD_REQUEST(<<"Bad Request">>);
|
||||||
|
'/connectors'(_, _) ->
|
||||||
|
?METHOD_NOT_ALLOWED.
|
||||||
|
|
||||||
'/connectors/:id'(get, #{bindings := #{id := Id}}) ->
|
'/connectors/:id'(get, #{bindings := #{id := Id}}) ->
|
||||||
?TRY_PARSE_ID(Id, lookup_from_all_nodes(ConnectorType, ConnectorName, 200));
|
?TRY_PARSE_ID(Id, lookup_from_all_nodes(ConnectorType, ConnectorName, 200));
|
||||||
|
@ -385,7 +389,9 @@ schema("/connectors_probe") ->
|
||||||
{error, not_found} ->
|
{error, not_found} ->
|
||||||
?CONNECTOR_NOT_FOUND(ConnectorType, ConnectorName)
|
?CONNECTOR_NOT_FOUND(ConnectorType, ConnectorName)
|
||||||
end
|
end
|
||||||
).
|
);
|
||||||
|
'/connectors/:id'(_, _) ->
|
||||||
|
?METHOD_NOT_ALLOWED.
|
||||||
|
|
||||||
'/connectors_probe'(post, Request) ->
|
'/connectors_probe'(post, Request) ->
|
||||||
RequestMeta = #{module => ?MODULE, method => post, path => "/connectors_probe"},
|
RequestMeta = #{module => ?MODULE, method => post, path => "/connectors_probe"},
|
||||||
|
@ -411,7 +417,9 @@ schema("/connectors_probe") ->
|
||||||
end;
|
end;
|
||||||
BadRequest ->
|
BadRequest ->
|
||||||
redact(BadRequest)
|
redact(BadRequest)
|
||||||
end.
|
end;
|
||||||
|
'/connectors_probe'(_, _) ->
|
||||||
|
?METHOD_NOT_ALLOWED.
|
||||||
|
|
||||||
maybe_deobfuscate_connector_probe(
|
maybe_deobfuscate_connector_probe(
|
||||||
#{<<"type">> := ConnectorType, <<"name">> := ConnectorName} = Params
|
#{<<"type">> := ConnectorType, <<"name">> := ConnectorName} = Params
|
||||||
|
|
|
@ -28,6 +28,8 @@
|
||||||
|
|
||||||
-define(NOT_FOUND(REASON), {404, ?ERROR_MSG('NOT_FOUND', REASON)}).
|
-define(NOT_FOUND(REASON), {404, ?ERROR_MSG('NOT_FOUND', REASON)}).
|
||||||
|
|
||||||
|
-define(METHOD_NOT_ALLOWED, 405).
|
||||||
|
|
||||||
-define(INTERNAL_ERROR(REASON), {500, ?ERROR_MSG('INTERNAL_ERROR', REASON)}).
|
-define(INTERNAL_ERROR(REASON), {500, ?ERROR_MSG('INTERNAL_ERROR', REASON)}).
|
||||||
|
|
||||||
-define(NOT_IMPLEMENTED, 501).
|
-define(NOT_IMPLEMENTED, 501).
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
emqx_bridge_v2_api {
|
||||||
|
|
||||||
|
desc_api1.desc:
|
||||||
|
"""List all created bridges."""
|
||||||
|
|
||||||
|
desc_api1.label:
|
||||||
|
"""List All Bridges"""
|
||||||
|
|
||||||
|
desc_api2.desc:
|
||||||
|
"""Create a new bridge by type and name."""
|
||||||
|
|
||||||
|
desc_api2.label:
|
||||||
|
"""Create Bridge"""
|
||||||
|
|
||||||
|
desc_api3.desc:
|
||||||
|
"""Get a bridge by id."""
|
||||||
|
|
||||||
|
desc_api3.label:
|
||||||
|
"""Get Bridge"""
|
||||||
|
|
||||||
|
desc_api4.desc:
|
||||||
|
"""Update a bridge by id."""
|
||||||
|
|
||||||
|
desc_api4.label:
|
||||||
|
"""Update Bridge"""
|
||||||
|
|
||||||
|
desc_api5.desc:
|
||||||
|
"""Delete a bridge by id."""
|
||||||
|
|
||||||
|
desc_api5.label:
|
||||||
|
"""Delete Bridge"""
|
||||||
|
|
||||||
|
desc_api6.desc:
|
||||||
|
"""Reset a bridge metrics by id."""
|
||||||
|
|
||||||
|
desc_api6.label:
|
||||||
|
"""Reset Bridge Metrics"""
|
||||||
|
|
||||||
|
desc_api7.desc:
|
||||||
|
"""Stop/restart bridges on all nodes in the cluster."""
|
||||||
|
|
||||||
|
desc_api7.label:
|
||||||
|
"""Cluster Bridge Operate"""
|
||||||
|
|
||||||
|
desc_api8.desc:
|
||||||
|
"""Stop/restart bridges on a specific node."""
|
||||||
|
|
||||||
|
desc_api8.label:
|
||||||
|
"""Node Bridge Operate"""
|
||||||
|
|
||||||
|
desc_api9.desc:
|
||||||
|
"""Test creating a new bridge by given id.</br>
|
||||||
|
The id must be of format '{type}:{name}'."""
|
||||||
|
|
||||||
|
desc_api9.label:
|
||||||
|
"""Test Bridge Creation"""
|
||||||
|
|
||||||
|
desc_bridge_metrics.desc:
|
||||||
|
"""Get bridge metrics by id."""
|
||||||
|
|
||||||
|
desc_bridge_metrics.label:
|
||||||
|
"""Get Bridge Metrics"""
|
||||||
|
|
||||||
|
desc_enable_bridge.desc:
|
||||||
|
"""Enable or Disable bridges on all nodes in the cluster."""
|
||||||
|
|
||||||
|
desc_enable_bridge.label:
|
||||||
|
"""Cluster Bridge Enable"""
|
||||||
|
|
||||||
|
desc_param_path_enable.desc:
|
||||||
|
"""Whether to enable this bridge."""
|
||||||
|
|
||||||
|
desc_param_path_enable.label:
|
||||||
|
"""Enable bridge"""
|
||||||
|
|
||||||
|
desc_param_path_id.desc:
|
||||||
|
"""The bridge id. Must be of format {type}:{name}."""
|
||||||
|
|
||||||
|
desc_param_path_id.label:
|
||||||
|
"""Bridge ID"""
|
||||||
|
|
||||||
|
desc_param_path_node.desc:
|
||||||
|
"""The node name, e.g. 'emqx@127.0.0.1'."""
|
||||||
|
|
||||||
|
desc_param_path_node.label:
|
||||||
|
"""The node name"""
|
||||||
|
|
||||||
|
desc_param_path_operation_cluster.desc:
|
||||||
|
"""Operations can be one of: 'stop' or 'restart'."""
|
||||||
|
|
||||||
|
desc_param_path_operation_cluster.label:
|
||||||
|
"""Cluster Operation"""
|
||||||
|
|
||||||
|
desc_param_path_operation_on_node.desc:
|
||||||
|
"""Operations can be one of: 'stop' or 'restart'."""
|
||||||
|
|
||||||
|
desc_param_path_operation_on_node.label:
|
||||||
|
"""Node Operation """
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue