feat: add `/sources*` HTTP APIs
This commit is contained in:
parent
e6ccfa5b39
commit
28de7c89c7
|
@ -23,6 +23,7 @@
|
||||||
bridge_to_resource_type/1,
|
bridge_to_resource_type/1,
|
||||||
resource_id/1,
|
resource_id/1,
|
||||||
resource_id/2,
|
resource_id/2,
|
||||||
|
resource_id/3,
|
||||||
bridge_id/2,
|
bridge_id/2,
|
||||||
parse_bridge_id/1,
|
parse_bridge_id/1,
|
||||||
parse_bridge_id/2,
|
parse_bridge_id/2,
|
||||||
|
@ -62,6 +63,9 @@
|
||||||
?IS_BI_DIR_BRIDGE(TYPE)
|
?IS_BI_DIR_BRIDGE(TYPE)
|
||||||
).
|
).
|
||||||
|
|
||||||
|
-define(ROOT_KEY_ACTIONS, actions).
|
||||||
|
-define(ROOT_KEY_SOURCES, sources).
|
||||||
|
|
||||||
-if(?EMQX_RELEASE_EDITION == ee).
|
-if(?EMQX_RELEASE_EDITION == ee).
|
||||||
bridge_to_resource_type(BridgeType) when is_binary(BridgeType) ->
|
bridge_to_resource_type(BridgeType) when is_binary(BridgeType) ->
|
||||||
bridge_to_resource_type(binary_to_existing_atom(BridgeType, utf8));
|
bridge_to_resource_type(binary_to_existing_atom(BridgeType, utf8));
|
||||||
|
@ -85,11 +89,21 @@ bridge_impl_module(_BridgeType) -> undefined.
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
resource_id(BridgeId) when is_binary(BridgeId) ->
|
resource_id(BridgeId) when is_binary(BridgeId) ->
|
||||||
|
resource_id_for_kind(?ROOT_KEY_ACTIONS, BridgeId).
|
||||||
|
|
||||||
|
resource_id(BridgeType, BridgeName) ->
|
||||||
|
resource_id(?ROOT_KEY_ACTIONS, BridgeType, BridgeName).
|
||||||
|
|
||||||
|
resource_id(ConfRootKey, BridgeType, BridgeName) ->
|
||||||
|
BridgeId = bridge_id(BridgeType, BridgeName),
|
||||||
|
resource_id_for_kind(ConfRootKey, BridgeId).
|
||||||
|
|
||||||
|
resource_id_for_kind(ConfRootKey, BridgeId) when is_binary(BridgeId) ->
|
||||||
case binary:split(BridgeId, <<":">>) of
|
case binary:split(BridgeId, <<":">>) of
|
||||||
[Type, _Name] ->
|
[Type, _Name] ->
|
||||||
case emqx_bridge_v2:is_bridge_v2_type(Type) of
|
case emqx_bridge_v2:is_bridge_v2_type(Type) of
|
||||||
true ->
|
true ->
|
||||||
emqx_bridge_v2:bridge_v1_id_to_connector_resource_id(BridgeId);
|
emqx_bridge_v2:bridge_v1_id_to_connector_resource_id(ConfRootKey, BridgeId);
|
||||||
false ->
|
false ->
|
||||||
<<"bridge:", BridgeId/binary>>
|
<<"bridge:", BridgeId/binary>>
|
||||||
end;
|
end;
|
||||||
|
@ -97,10 +111,6 @@ resource_id(BridgeId) when is_binary(BridgeId) ->
|
||||||
invalid_data(<<"should be of pattern {type}:{name}, but got ", BridgeId/binary>>)
|
invalid_data(<<"should be of pattern {type}:{name}, but got ", BridgeId/binary>>)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
resource_id(BridgeType, BridgeName) ->
|
|
||||||
BridgeId = bridge_id(BridgeType, BridgeName),
|
|
||||||
resource_id(BridgeId).
|
|
||||||
|
|
||||||
bridge_id(BridgeType, BridgeName) ->
|
bridge_id(BridgeType, BridgeName) ->
|
||||||
Name = bin(BridgeName),
|
Name = bin(BridgeName),
|
||||||
Type = bin(BridgeType),
|
Type = bin(BridgeType),
|
||||||
|
|
|
@ -91,6 +91,7 @@
|
||||||
id/2,
|
id/2,
|
||||||
id/3,
|
id/3,
|
||||||
bridge_v1_is_valid/2,
|
bridge_v1_is_valid/2,
|
||||||
|
bridge_v1_is_valid/3,
|
||||||
extract_connector_id_from_bridge_v2_id/1
|
extract_connector_id_from_bridge_v2_id/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
@ -128,6 +129,7 @@
|
||||||
%% Exception from the naming convention:
|
%% Exception from the naming convention:
|
||||||
bridge_v2_type_to_bridge_v1_type/2,
|
bridge_v2_type_to_bridge_v1_type/2,
|
||||||
bridge_v1_id_to_connector_resource_id/1,
|
bridge_v1_id_to_connector_resource_id/1,
|
||||||
|
bridge_v1_id_to_connector_resource_id/2,
|
||||||
bridge_v1_enable_disable/3,
|
bridge_v1_enable_disable/3,
|
||||||
bridge_v1_restart/2,
|
bridge_v1_restart/2,
|
||||||
bridge_v1_stop/2,
|
bridge_v1_stop/2,
|
||||||
|
@ -567,7 +569,7 @@ connector_operation_helper(ConfRootKey, BridgeV2Type, Name, ConnectorOpFun, DoHe
|
||||||
ConfRootKey,
|
ConfRootKey,
|
||||||
BridgeV2Type,
|
BridgeV2Type,
|
||||||
Name,
|
Name,
|
||||||
lookup_conf(BridgeV2Type, Name),
|
lookup_conf(ConfRootKey, BridgeV2Type, Name),
|
||||||
ConnectorOpFun,
|
ConnectorOpFun,
|
||||||
DoHealthCheck
|
DoHealthCheck
|
||||||
).
|
).
|
||||||
|
@ -1191,8 +1193,11 @@ unpack_bridge_conf(Type, PackedConf, TopLevelConf) ->
|
||||||
%% * The corresponding bridge v2 should exist
|
%% * The corresponding bridge v2 should exist
|
||||||
%% * The connector for the bridge v2 should have exactly one channel
|
%% * The connector for the bridge v2 should have exactly one channel
|
||||||
bridge_v1_is_valid(BridgeV1Type, BridgeName) ->
|
bridge_v1_is_valid(BridgeV1Type, BridgeName) ->
|
||||||
|
bridge_v1_is_valid(?ROOT_KEY_ACTIONS, BridgeV1Type, BridgeName).
|
||||||
|
|
||||||
|
bridge_v1_is_valid(ConfRootKey, BridgeV1Type, BridgeName) ->
|
||||||
BridgeV2Type = ?MODULE:bridge_v1_type_to_bridge_v2_type(BridgeV1Type),
|
BridgeV2Type = ?MODULE:bridge_v1_type_to_bridge_v2_type(BridgeV1Type),
|
||||||
case lookup_conf(BridgeV2Type, BridgeName) of
|
case lookup_conf(ConfRootKey, BridgeV2Type, BridgeName) of
|
||||||
{error, _} ->
|
{error, _} ->
|
||||||
%% If the bridge v2 does not exist, it is a valid bridge v1
|
%% If the bridge v2 does not exist, it is a valid bridge v1
|
||||||
true;
|
true;
|
||||||
|
@ -1241,17 +1246,20 @@ bridge_v1_list_and_transform() ->
|
||||||
|
|
||||||
bridge_v1_lookup_and_transform(ActionType, Name) ->
|
bridge_v1_lookup_and_transform(ActionType, Name) ->
|
||||||
case lookup_actions_or_sources(ActionType, Name) of
|
case lookup_actions_or_sources(ActionType, Name) of
|
||||||
{ok, ConfRootName,
|
{ok, ConfRootKey,
|
||||||
#{raw_config := #{<<"connector">> := ConnectorName} = RawConfig} = ActionConfig} ->
|
#{raw_config := #{<<"connector">> := ConnectorName} = RawConfig} = ActionConfig} ->
|
||||||
BridgeV1Type = ?MODULE:bridge_v2_type_to_bridge_v1_type(ActionType, RawConfig),
|
BridgeV1Type = ?MODULE:bridge_v2_type_to_bridge_v1_type(ActionType, RawConfig),
|
||||||
HasBridgeV1Equivalent = has_bridge_v1_equivalent(ActionType),
|
HasBridgeV1Equivalent = has_bridge_v1_equivalent(ActionType),
|
||||||
case HasBridgeV1Equivalent andalso ?MODULE:bridge_v1_is_valid(BridgeV1Type, Name) of
|
case
|
||||||
|
HasBridgeV1Equivalent andalso
|
||||||
|
?MODULE:bridge_v1_is_valid(ConfRootKey, BridgeV1Type, Name)
|
||||||
|
of
|
||||||
true ->
|
true ->
|
||||||
ConnectorType = connector_type(ActionType),
|
ConnectorType = connector_type(ActionType),
|
||||||
case emqx_connector:lookup(ConnectorType, ConnectorName) of
|
case emqx_connector:lookup(ConnectorType, ConnectorName) of
|
||||||
{ok, Connector} ->
|
{ok, Connector} ->
|
||||||
bridge_v1_lookup_and_transform_helper(
|
bridge_v1_lookup_and_transform_helper(
|
||||||
ConfRootName,
|
ConfRootKey,
|
||||||
BridgeV1Type,
|
BridgeV1Type,
|
||||||
Name,
|
Name,
|
||||||
ActionType,
|
ActionType,
|
||||||
|
@ -1718,11 +1726,14 @@ connector_has_channels(BridgeV2Type, ConnectorName) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
bridge_v1_id_to_connector_resource_id(BridgeId) ->
|
bridge_v1_id_to_connector_resource_id(BridgeId) ->
|
||||||
|
bridge_v1_id_to_connector_resource_id(?ROOT_KEY_ACTIONS, BridgeId).
|
||||||
|
|
||||||
|
bridge_v1_id_to_connector_resource_id(ConfRootKey, BridgeId) ->
|
||||||
case binary:split(BridgeId, <<":">>) of
|
case binary:split(BridgeId, <<":">>) of
|
||||||
[Type, Name] ->
|
[Type, Name] ->
|
||||||
BridgeV2Type = bin(bridge_v1_type_to_bridge_v2_type(Type)),
|
BridgeV2Type = bin(bridge_v1_type_to_bridge_v2_type(Type)),
|
||||||
ConnectorName =
|
ConnectorName =
|
||||||
case lookup_conf(BridgeV2Type, Name) of
|
case lookup_conf(ConfRootKey, BridgeV2Type, Name) of
|
||||||
#{connector := Con} ->
|
#{connector := Con} ->
|
||||||
Con;
|
Con;
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
namespace/0
|
namespace/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
%% API callbacks
|
%% API callbacks : actions
|
||||||
-export([
|
-export([
|
||||||
'/actions'/2,
|
'/actions'/2,
|
||||||
'/actions/:id'/2,
|
'/actions/:id'/2,
|
||||||
|
@ -49,6 +49,18 @@
|
||||||
'/actions_probe'/2,
|
'/actions_probe'/2,
|
||||||
'/action_types'/2
|
'/action_types'/2
|
||||||
]).
|
]).
|
||||||
|
%% API callbacks : sources
|
||||||
|
-export([
|
||||||
|
'/sources'/2,
|
||||||
|
'/sources/:id'/2,
|
||||||
|
'/sources/:id/metrics'/2,
|
||||||
|
'/sources/:id/metrics/reset'/2,
|
||||||
|
'/sources/:id/enable/:enable'/2,
|
||||||
|
'/sources/:id/:operation'/2,
|
||||||
|
'/nodes/:node/sources/:id/:operation'/2,
|
||||||
|
'/sources_probe'/2,
|
||||||
|
'/source_types'/2
|
||||||
|
]).
|
||||||
|
|
||||||
%% BpAPI / RPC Targets
|
%% BpAPI / RPC Targets
|
||||||
-export([
|
-export([
|
||||||
|
@ -81,13 +93,16 @@
|
||||||
end
|
end
|
||||||
).
|
).
|
||||||
|
|
||||||
namespace() -> "actions".
|
namespace() -> "actions_and_sources".
|
||||||
|
|
||||||
api_spec() ->
|
api_spec() ->
|
||||||
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
|
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
|
||||||
|
|
||||||
paths() ->
|
paths() ->
|
||||||
[
|
[
|
||||||
|
%%=============
|
||||||
|
%% Actions
|
||||||
|
%%=============
|
||||||
"/actions",
|
"/actions",
|
||||||
"/actions/:id",
|
"/actions/:id",
|
||||||
"/actions/:id/enable/:enable",
|
"/actions/:id/enable/:enable",
|
||||||
|
@ -98,7 +113,21 @@ paths() ->
|
||||||
"/actions/:id/metrics",
|
"/actions/:id/metrics",
|
||||||
"/actions/:id/metrics/reset",
|
"/actions/:id/metrics/reset",
|
||||||
"/actions_probe",
|
"/actions_probe",
|
||||||
"/action_types"
|
"/action_types",
|
||||||
|
%%=============
|
||||||
|
%% Sources
|
||||||
|
%%=============
|
||||||
|
"/sources",
|
||||||
|
"/sources/:id",
|
||||||
|
"/sources/:id/enable/:enable",
|
||||||
|
"/sources/:id/:operation",
|
||||||
|
"/nodes/:node/sources/:id/:operation",
|
||||||
|
%% %% Caveat: metrics paths must come *after* `/:operation', otherwise minirest will
|
||||||
|
%% %% try to match the latter first, trying to interpret `metrics' as an operation...
|
||||||
|
"/sources/:id/metrics",
|
||||||
|
"/sources/:id/metrics/reset",
|
||||||
|
"/sources_probe"
|
||||||
|
%% "/source_types"
|
||||||
].
|
].
|
||||||
|
|
||||||
error_schema(Code, Message) ->
|
error_schema(Code, Message) ->
|
||||||
|
@ -111,17 +140,28 @@ error_schema(Codes, Message, ExtraFields) when is_list(Message) ->
|
||||||
error_schema(Codes, Message, ExtraFields) when is_list(Codes) andalso is_binary(Message) ->
|
error_schema(Codes, Message, ExtraFields) when is_list(Codes) andalso is_binary(Message) ->
|
||||||
ExtraFields ++ emqx_dashboard_swagger:error_codes(Codes, Message).
|
ExtraFields ++ emqx_dashboard_swagger:error_codes(Codes, Message).
|
||||||
|
|
||||||
get_response_body_schema() ->
|
actions_get_response_body_schema() ->
|
||||||
emqx_dashboard_swagger:schema_with_examples(
|
emqx_dashboard_swagger:schema_with_examples(
|
||||||
emqx_bridge_v2_schema:get_response(),
|
emqx_bridge_v2_schema:actions_get_response(),
|
||||||
bridge_info_examples(get)
|
bridge_info_examples(get, ?ROOT_KEY_ACTIONS)
|
||||||
).
|
).
|
||||||
|
|
||||||
bridge_info_examples(Method) ->
|
sources_get_response_body_schema() ->
|
||||||
emqx_bridge_v2_schema:examples(Method).
|
emqx_dashboard_swagger:schema_with_examples(
|
||||||
|
emqx_bridge_v2_schema:sources_get_response(),
|
||||||
|
bridge_info_examples(get, ?ROOT_KEY_SOURCES)
|
||||||
|
).
|
||||||
|
|
||||||
bridge_info_array_example(Method) ->
|
bridge_info_examples(Method, ?ROOT_KEY_ACTIONS) ->
|
||||||
lists:map(fun(#{value := Config}) -> Config end, maps:values(bridge_info_examples(Method))).
|
emqx_bridge_v2_schema:actions_examples(Method);
|
||||||
|
bridge_info_examples(Method, ?ROOT_KEY_SOURCES) ->
|
||||||
|
emqx_bridge_v2_schema:sources_examples(Method).
|
||||||
|
|
||||||
|
bridge_info_array_example(Method, ConfRootKey) ->
|
||||||
|
lists:map(
|
||||||
|
fun(#{value := Config}) -> Config end,
|
||||||
|
maps:values(bridge_info_examples(Method, ConfRootKey))
|
||||||
|
).
|
||||||
|
|
||||||
param_path_id() ->
|
param_path_id() ->
|
||||||
{id,
|
{id,
|
||||||
|
@ -195,6 +235,9 @@ param_path_enable() ->
|
||||||
}
|
}
|
||||||
)}.
|
)}.
|
||||||
|
|
||||||
|
%%================================================================================
|
||||||
|
%% Actions
|
||||||
|
%%================================================================================
|
||||||
schema("/actions") ->
|
schema("/actions") ->
|
||||||
#{
|
#{
|
||||||
'operationId' => '/actions',
|
'operationId' => '/actions',
|
||||||
|
@ -204,8 +247,8 @@ schema("/actions") ->
|
||||||
description => ?DESC("desc_api1"),
|
description => ?DESC("desc_api1"),
|
||||||
responses => #{
|
responses => #{
|
||||||
200 => emqx_dashboard_swagger:schema_with_example(
|
200 => emqx_dashboard_swagger:schema_with_example(
|
||||||
array(emqx_bridge_v2_schema:get_response()),
|
array(emqx_bridge_v2_schema:actions_get_response()),
|
||||||
bridge_info_array_example(get)
|
bridge_info_array_example(get, ?ROOT_KEY_ACTIONS)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -214,11 +257,11 @@ schema("/actions") ->
|
||||||
summary => <<"Create bridge">>,
|
summary => <<"Create bridge">>,
|
||||||
description => ?DESC("desc_api2"),
|
description => ?DESC("desc_api2"),
|
||||||
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
||||||
emqx_bridge_v2_schema:post_request(),
|
emqx_bridge_v2_schema:actions_post_request(),
|
||||||
bridge_info_examples(post)
|
bridge_info_examples(post, ?ROOT_KEY_ACTIONS)
|
||||||
),
|
),
|
||||||
responses => #{
|
responses => #{
|
||||||
201 => get_response_body_schema(),
|
201 => actions_get_response_body_schema(),
|
||||||
400 => error_schema('ALREADY_EXISTS', "Bridge already exists")
|
400 => error_schema('ALREADY_EXISTS', "Bridge already exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,7 +275,7 @@ schema("/actions/:id") ->
|
||||||
description => ?DESC("desc_api3"),
|
description => ?DESC("desc_api3"),
|
||||||
parameters => [param_path_id()],
|
parameters => [param_path_id()],
|
||||||
responses => #{
|
responses => #{
|
||||||
200 => get_response_body_schema(),
|
200 => actions_get_response_body_schema(),
|
||||||
404 => error_schema('NOT_FOUND', "Bridge not found")
|
404 => error_schema('NOT_FOUND', "Bridge not found")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -242,11 +285,11 @@ schema("/actions/:id") ->
|
||||||
description => ?DESC("desc_api4"),
|
description => ?DESC("desc_api4"),
|
||||||
parameters => [param_path_id()],
|
parameters => [param_path_id()],
|
||||||
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
||||||
emqx_bridge_v2_schema:put_request(),
|
emqx_bridge_v2_schema:actions_put_request(),
|
||||||
bridge_info_examples(put)
|
bridge_info_examples(put, ?ROOT_KEY_ACTIONS)
|
||||||
),
|
),
|
||||||
responses => #{
|
responses => #{
|
||||||
200 => get_response_body_schema(),
|
200 => actions_get_response_body_schema(),
|
||||||
404 => error_schema('NOT_FOUND', "Bridge not found"),
|
404 => error_schema('NOT_FOUND', "Bridge not found"),
|
||||||
400 => error_schema('BAD_REQUEST', "Update bridge failed")
|
400 => error_schema('BAD_REQUEST', "Update bridge failed")
|
||||||
}
|
}
|
||||||
|
@ -371,8 +414,8 @@ schema("/actions_probe") ->
|
||||||
desc => ?DESC("desc_api9"),
|
desc => ?DESC("desc_api9"),
|
||||||
summary => <<"Test creating bridge">>,
|
summary => <<"Test creating bridge">>,
|
||||||
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
||||||
emqx_bridge_v2_schema:post_request(),
|
emqx_bridge_v2_schema:actions_post_request(),
|
||||||
bridge_info_examples(post)
|
bridge_info_examples(post, ?ROOT_KEY_ACTIONS)
|
||||||
),
|
),
|
||||||
responses => #{
|
responses => #{
|
||||||
204 => <<"Test bridge OK">>,
|
204 => <<"Test bridge OK">>,
|
||||||
|
@ -389,12 +432,223 @@ schema("/action_types") ->
|
||||||
summary => <<"List available action types">>,
|
summary => <<"List available action types">>,
|
||||||
responses => #{
|
responses => #{
|
||||||
200 => emqx_dashboard_swagger:schema_with_examples(
|
200 => emqx_dashboard_swagger:schema_with_examples(
|
||||||
array(emqx_bridge_v2_schema:types_sc()),
|
array(emqx_bridge_v2_schema:action_types_sc()),
|
||||||
#{
|
#{
|
||||||
<<"types">> =>
|
<<"types">> =>
|
||||||
#{
|
#{
|
||||||
summary => <<"Action types">>,
|
summary => <<"Action types">>,
|
||||||
value => emqx_bridge_v2_schema:types()
|
value => emqx_bridge_v2_schema:action_types()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
%%================================================================================
|
||||||
|
%% Sources
|
||||||
|
%%================================================================================
|
||||||
|
schema("/sources") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/sources',
|
||||||
|
get => #{
|
||||||
|
tags => [<<"sources">>],
|
||||||
|
summary => <<"List sources">>,
|
||||||
|
description => ?DESC("desc_api1"),
|
||||||
|
responses => #{
|
||||||
|
%% FIXME: examples
|
||||||
|
200 => emqx_dashboard_swagger:schema_with_example(
|
||||||
|
array(emqx_bridge_v2_schema:sources_get_response()),
|
||||||
|
bridge_info_array_example(get, ?ROOT_KEY_SOURCES)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
post => #{
|
||||||
|
tags => [<<"sources">>],
|
||||||
|
summary => <<"Create source">>,
|
||||||
|
description => ?DESC("desc_api2"),
|
||||||
|
%% FIXME: examples
|
||||||
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
||||||
|
emqx_bridge_v2_schema:sources_post_request(),
|
||||||
|
bridge_info_examples(post, ?ROOT_KEY_SOURCES)
|
||||||
|
),
|
||||||
|
responses => #{
|
||||||
|
201 => sources_get_response_body_schema(),
|
||||||
|
400 => error_schema('ALREADY_EXISTS', "Source already exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
schema("/sources/:id") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/sources/:id',
|
||||||
|
get => #{
|
||||||
|
tags => [<<"sources">>],
|
||||||
|
summary => <<"Get source">>,
|
||||||
|
description => ?DESC("desc_api3"),
|
||||||
|
parameters => [param_path_id()],
|
||||||
|
responses => #{
|
||||||
|
200 => sources_get_response_body_schema(),
|
||||||
|
404 => error_schema('NOT_FOUND', "Source not found")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
put => #{
|
||||||
|
tags => [<<"sources">>],
|
||||||
|
summary => <<"Update source">>,
|
||||||
|
description => ?DESC("desc_api4"),
|
||||||
|
parameters => [param_path_id()],
|
||||||
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
||||||
|
emqx_bridge_v2_schema:sources_put_request(),
|
||||||
|
bridge_info_examples(put, ?ROOT_KEY_SOURCES)
|
||||||
|
),
|
||||||
|
responses => #{
|
||||||
|
200 => sources_get_response_body_schema(),
|
||||||
|
404 => error_schema('NOT_FOUND', "Source not found"),
|
||||||
|
400 => error_schema('BAD_REQUEST', "Update source failed")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delete => #{
|
||||||
|
tags => [<<"sources">>],
|
||||||
|
summary => <<"Delete source">>,
|
||||||
|
description => ?DESC("desc_api5"),
|
||||||
|
parameters => [param_path_id(), param_qs_delete_cascade()],
|
||||||
|
responses => #{
|
||||||
|
204 => <<"Source deleted">>,
|
||||||
|
400 => error_schema(
|
||||||
|
'BAD_REQUEST',
|
||||||
|
"Cannot delete bridge while active rules are defined for this source",
|
||||||
|
[{rules, mk(array(string()), #{desc => "Dependent Rule IDs"})}]
|
||||||
|
),
|
||||||
|
404 => error_schema('NOT_FOUND', "Source not found"),
|
||||||
|
503 => error_schema('SERVICE_UNAVAILABLE', "Service unavailable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
schema("/sources/:id/metrics") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/sources/:id/metrics',
|
||||||
|
get => #{
|
||||||
|
tags => [<<"sources">>],
|
||||||
|
summary => <<"Get source metrics">>,
|
||||||
|
description => ?DESC("desc_bridge_metrics"),
|
||||||
|
parameters => [param_path_id()],
|
||||||
|
responses => #{
|
||||||
|
200 => emqx_bridge_schema:metrics_fields(),
|
||||||
|
404 => error_schema('NOT_FOUND', "Source not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
schema("/sources/:id/metrics/reset") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/sources/:id/metrics/reset',
|
||||||
|
put => #{
|
||||||
|
tags => [<<"sources">>],
|
||||||
|
summary => <<"Reset source metrics">>,
|
||||||
|
description => ?DESC("desc_api6"),
|
||||||
|
parameters => [param_path_id()],
|
||||||
|
responses => #{
|
||||||
|
204 => <<"Reset success">>,
|
||||||
|
404 => error_schema('NOT_FOUND', "Source not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
schema("/sources/:id/enable/:enable") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/sources/:id/enable/:enable',
|
||||||
|
put =>
|
||||||
|
#{
|
||||||
|
tags => [<<"sources">>],
|
||||||
|
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("/sources/:id/:operation") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/sources/:id/:operation',
|
||||||
|
post => #{
|
||||||
|
tags => [<<"sources">>],
|
||||||
|
summary => <<"Manually start a 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/sources/:id/:operation") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/nodes/:node/sources/:id/:operation',
|
||||||
|
post => #{
|
||||||
|
tags => [<<"sources">>],
|
||||||
|
summary => <<"Manually start a bridge on a given node">>,
|
||||||
|
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("/sources_probe") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/sources_probe',
|
||||||
|
post => #{
|
||||||
|
tags => [<<"sources">>],
|
||||||
|
desc => ?DESC("desc_api9"),
|
||||||
|
summary => <<"Test creating bridge">>,
|
||||||
|
'requestBody' => emqx_dashboard_swagger:schema_with_examples(
|
||||||
|
emqx_bridge_v2_schema:sources_post_request(),
|
||||||
|
bridge_info_examples(post, ?ROOT_KEY_SOURCES)
|
||||||
|
),
|
||||||
|
responses => #{
|
||||||
|
204 => <<"Test bridge OK">>,
|
||||||
|
400 => error_schema(['TEST_FAILED'], "bridge test failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
schema("/source_types") ->
|
||||||
|
#{
|
||||||
|
'operationId' => '/source_types',
|
||||||
|
get => #{
|
||||||
|
tags => [<<"sources">>],
|
||||||
|
desc => ?DESC("desc_api10"),
|
||||||
|
summary => <<"List available source types">>,
|
||||||
|
responses => #{
|
||||||
|
200 => emqx_dashboard_swagger:schema_with_examples(
|
||||||
|
array(emqx_bridge_v2_schema:action_types_sc()),
|
||||||
|
#{
|
||||||
|
<<"types">> =>
|
||||||
|
#{
|
||||||
|
summary => <<"Source types">>,
|
||||||
|
value => emqx_bridge_v2_schema:action_types()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -402,6 +656,12 @@ schema("/action_types") ->
|
||||||
}
|
}
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% Thin Handlers
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%%================================================================================
|
||||||
|
%% Actions
|
||||||
|
%%================================================================================
|
||||||
'/actions'(post, #{body := #{<<"type">> := BridgeType, <<"name">> := BridgeName} = Conf0}) ->
|
'/actions'(post, #{body := #{<<"type">> := BridgeType, <<"name">> := BridgeName} = Conf0}) ->
|
||||||
handle_create(?ROOT_KEY_ACTIONS, BridgeType, BridgeName, Conf0);
|
handle_create(?ROOT_KEY_ACTIONS, BridgeType, BridgeName, Conf0);
|
||||||
'/actions'(get, _Params) ->
|
'/actions'(get, _Params) ->
|
||||||
|
@ -439,7 +699,48 @@ schema("/action_types") ->
|
||||||
handle_probe(?ROOT_KEY_ACTIONS, Request).
|
handle_probe(?ROOT_KEY_ACTIONS, Request).
|
||||||
|
|
||||||
'/action_types'(get, _Request) ->
|
'/action_types'(get, _Request) ->
|
||||||
?OK(emqx_bridge_v2_schema:types()).
|
?OK(emqx_bridge_v2_schema:action_types()).
|
||||||
|
%%================================================================================
|
||||||
|
%% Sources
|
||||||
|
%%================================================================================
|
||||||
|
'/sources'(post, #{body := #{<<"type">> := BridgeType, <<"name">> := BridgeName} = Conf0}) ->
|
||||||
|
handle_create(?ROOT_KEY_SOURCES, BridgeType, BridgeName, Conf0);
|
||||||
|
'/sources'(get, _Params) ->
|
||||||
|
handle_list(?ROOT_KEY_SOURCES).
|
||||||
|
|
||||||
|
'/sources/:id'(get, #{bindings := #{id := Id}}) ->
|
||||||
|
?TRY_PARSE_ID(Id, lookup_from_all_nodes(?ROOT_KEY_SOURCES, BridgeType, BridgeName, 200));
|
||||||
|
'/sources/:id'(put, #{bindings := #{id := Id}, body := Conf0}) ->
|
||||||
|
handle_update(?ROOT_KEY_SOURCES, Id, Conf0);
|
||||||
|
'/sources/:id'(delete, #{bindings := #{id := Id}, query_string := Qs}) ->
|
||||||
|
handle_delete(?ROOT_KEY_SOURCES, Id, Qs).
|
||||||
|
|
||||||
|
'/sources/:id/metrics'(get, #{bindings := #{id := Id}}) ->
|
||||||
|
?TRY_PARSE_ID(Id, get_metrics_from_all_nodes(?ROOT_KEY_SOURCES, BridgeType, BridgeName)).
|
||||||
|
|
||||||
|
'/sources/:id/metrics/reset'(put, #{bindings := #{id := Id}}) ->
|
||||||
|
handle_reset_metrics(?ROOT_KEY_SOURCES, Id).
|
||||||
|
|
||||||
|
'/sources/:id/enable/:enable'(put, #{bindings := #{id := Id, enable := Enable}}) ->
|
||||||
|
handle_disable_enable(?ROOT_KEY_SOURCES, Id, Enable).
|
||||||
|
|
||||||
|
'/sources/:id/:operation'(post, #{
|
||||||
|
bindings :=
|
||||||
|
#{id := Id, operation := Op}
|
||||||
|
}) ->
|
||||||
|
handle_operation(?ROOT_KEY_SOURCES, Id, Op).
|
||||||
|
|
||||||
|
'/nodes/:node/sources/:id/:operation'(post, #{
|
||||||
|
bindings :=
|
||||||
|
#{id := Id, operation := Op, node := Node}
|
||||||
|
}) ->
|
||||||
|
handle_node_operation(?ROOT_KEY_SOURCES, Node, Id, Op).
|
||||||
|
|
||||||
|
'/sources_probe'(post, Request) ->
|
||||||
|
handle_probe(?ROOT_KEY_SOURCES, Request).
|
||||||
|
|
||||||
|
'/source_types'(get, _Request) ->
|
||||||
|
?OK(emqx_bridge_v2_schema:source_types()).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Handlers
|
%% Handlers
|
||||||
|
@ -451,7 +752,7 @@ handle_list(ConfRootKey) ->
|
||||||
case is_ok(NodeReplies) of
|
case is_ok(NodeReplies) of
|
||||||
{ok, NodeBridges} ->
|
{ok, NodeBridges} ->
|
||||||
AllBridges = [
|
AllBridges = [
|
||||||
[format_resource(Data, Node) || Data <- Bridges]
|
[format_resource(ConfRootKey, Data, Node) || Data <- Bridges]
|
||||||
|| {Node, Bridges} <- lists:zip(Nodes, NodeBridges)
|
|| {Node, Bridges} <- lists:zip(Nodes, NodeBridges)
|
||||||
],
|
],
|
||||||
?OK(zip_bridges(AllBridges));
|
?OK(zip_bridges(AllBridges));
|
||||||
|
@ -574,7 +875,12 @@ handle_node_operation(ConfRootKey, Node, Id, Op) ->
|
||||||
).
|
).
|
||||||
|
|
||||||
handle_probe(ConfRootKey, Request) ->
|
handle_probe(ConfRootKey, Request) ->
|
||||||
RequestMeta = #{module => ?MODULE, method => post, path => "/actions_probe"},
|
Path =
|
||||||
|
case ConfRootKey of
|
||||||
|
?ROOT_KEY_ACTIONS -> "/actions_probe";
|
||||||
|
?ROOT_KEY_SOURCES -> "/sources_probe"
|
||||||
|
end,
|
||||||
|
RequestMeta = #{module => ?MODULE, method => post, path => Path},
|
||||||
case emqx_dashboard_swagger:filter_check_request_and_translate_body(Request, RequestMeta) of
|
case emqx_dashboard_swagger:filter_check_request_and_translate_body(Request, RequestMeta) of
|
||||||
{ok, #{body := #{<<"type">> := Type} = Params}} ->
|
{ok, #{body := #{<<"type">> := Type} = Params}} ->
|
||||||
Params1 = maybe_deobfuscate_bridge_probe(Params),
|
Params1 = maybe_deobfuscate_bridge_probe(Params),
|
||||||
|
@ -664,8 +970,8 @@ get_metrics_from_all_nodes(ConfRootKey, Type, Name) ->
|
||||||
?INTERNAL_ERROR(Reason)
|
?INTERNAL_ERROR(Reason)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
operation_func(all, start) -> v2_start_bridge_to_all_nodes_v6;
|
operation_func(all, start) -> v2_start_bridge_on_all_nodes_v6;
|
||||||
operation_func(_Node, start) -> v2_start_bridge_to_node_v6;
|
operation_func(_Node, start) -> v2_start_bridge_on_node_v6;
|
||||||
operation_func(all, lookup) -> v2_lookup_from_all_nodes_v6;
|
operation_func(all, lookup) -> v2_lookup_from_all_nodes_v6;
|
||||||
operation_func(all, list) -> v2_list_bridges_on_nodes_v6;
|
operation_func(all, list) -> v2_list_bridges_on_nodes_v6;
|
||||||
operation_func(all, get_metrics) -> v2_get_metrics_from_all_nodes_v6.
|
operation_func(all, get_metrics) -> v2_get_metrics_from_all_nodes_v6.
|
||||||
|
@ -825,7 +1131,7 @@ aggregate_status(AllStatus) ->
|
||||||
%% RPC Target
|
%% RPC Target
|
||||||
lookup_from_local_node(BridgeType, BridgeName) ->
|
lookup_from_local_node(BridgeType, BridgeName) ->
|
||||||
case emqx_bridge_v2:lookup(BridgeType, BridgeName) of
|
case emqx_bridge_v2:lookup(BridgeType, BridgeName) of
|
||||||
{ok, Res} -> {ok, format_resource(Res, node())};
|
{ok, Res} -> {ok, format_resource(?ROOT_KEY_ACTIONS, Res, node())};
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -833,7 +1139,7 @@ lookup_from_local_node(BridgeType, BridgeName) ->
|
||||||
-spec lookup_from_local_node_v6(emqx_bridge_v2:root_cfg_key(), _, _) -> _.
|
-spec lookup_from_local_node_v6(emqx_bridge_v2:root_cfg_key(), _, _) -> _.
|
||||||
lookup_from_local_node_v6(ConfRootKey, BridgeType, BridgeName) ->
|
lookup_from_local_node_v6(ConfRootKey, BridgeType, BridgeName) ->
|
||||||
case emqx_bridge_v2:lookup(ConfRootKey, BridgeType, BridgeName) of
|
case emqx_bridge_v2:lookup(ConfRootKey, BridgeType, BridgeName) of
|
||||||
{ok, Res} -> {ok, format_resource(Res, node())};
|
{ok, Res} -> {ok, format_resource(ConfRootKey, Res, node())};
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -847,6 +1153,7 @@ get_metrics_from_local_node_v6(ConfRootKey, Type, Name) ->
|
||||||
|
|
||||||
%% resource
|
%% resource
|
||||||
format_resource(
|
format_resource(
|
||||||
|
ConfRootKey,
|
||||||
#{
|
#{
|
||||||
type := Type,
|
type := Type,
|
||||||
name := Name,
|
name := Name,
|
||||||
|
@ -857,7 +1164,7 @@ format_resource(
|
||||||
},
|
},
|
||||||
Node
|
Node
|
||||||
) ->
|
) ->
|
||||||
RawConf = fill_defaults(Type, RawConf0),
|
RawConf = fill_defaults(ConfRootKey, Type, RawConf0),
|
||||||
redact(
|
redact(
|
||||||
maps:merge(
|
maps:merge(
|
||||||
RawConf#{
|
RawConf#{
|
||||||
|
@ -988,17 +1295,18 @@ aggregate_metrics(
|
||||||
M17 + N17
|
M17 + N17
|
||||||
).
|
).
|
||||||
|
|
||||||
fill_defaults(Type, RawConf) ->
|
fill_defaults(ConfRootKey, Type, RawConf) ->
|
||||||
PackedConf = pack_bridge_conf(Type, RawConf),
|
PackedConf = pack_bridge_conf(ConfRootKey, Type, RawConf),
|
||||||
FullConf = emqx_config:fill_defaults(emqx_bridge_v2_schema, PackedConf, #{}),
|
FullConf = emqx_config:fill_defaults(emqx_bridge_v2_schema, PackedConf, #{}),
|
||||||
unpack_bridge_conf(Type, FullConf).
|
unpack_bridge_conf(ConfRootKey, Type, FullConf).
|
||||||
|
|
||||||
pack_bridge_conf(Type, RawConf) ->
|
pack_bridge_conf(ConfRootKey, Type, RawConf) ->
|
||||||
#{<<"actions">> => #{bin(Type) => #{<<"foo">> => RawConf}}}.
|
#{bin(ConfRootKey) => #{bin(Type) => #{<<"foo">> => RawConf}}}.
|
||||||
|
|
||||||
unpack_bridge_conf(Type, PackedConf) ->
|
unpack_bridge_conf(ConfRootKey, Type, PackedConf) ->
|
||||||
|
ConfRootKeyBin = bin(ConfRootKey),
|
||||||
TypeBin = bin(Type),
|
TypeBin = bin(Type),
|
||||||
#{<<"actions">> := Bridges} = PackedConf,
|
#{ConfRootKeyBin := Bridges} = PackedConf,
|
||||||
#{<<"foo">> := RawConf} = maps:get(TypeBin, Bridges),
|
#{<<"foo">> := RawConf} = maps:get(TypeBin, Bridges),
|
||||||
RawConf.
|
RawConf.
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,8 @@
|
||||||
v2_lookup_from_all_nodes_v6/4,
|
v2_lookup_from_all_nodes_v6/4,
|
||||||
v2_list_bridges_on_nodes_v6/2,
|
v2_list_bridges_on_nodes_v6/2,
|
||||||
v2_get_metrics_from_all_nodes_v6/4,
|
v2_get_metrics_from_all_nodes_v6/4,
|
||||||
v2_start_bridge_to_node_v6/4,
|
v2_start_bridge_on_node_v6/4,
|
||||||
v2_start_bridge_to_all_nodes_v6/4
|
v2_start_bridge_on_all_nodes_v6/4
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include_lib("emqx/include/bpapi.hrl").
|
-include_lib("emqx/include/bpapi.hrl").
|
||||||
|
@ -173,9 +173,9 @@ v2_get_metrics_from_all_nodes_v6(Nodes, ConfRootKey, ActionType, ActionName) ->
|
||||||
?TIMEOUT
|
?TIMEOUT
|
||||||
).
|
).
|
||||||
|
|
||||||
-spec v2_start_bridge_to_all_nodes_v6([node()], emqx_bridge_v2:root_cfg_key(), key(), key()) ->
|
-spec v2_start_bridge_on_all_nodes_v6([node()], emqx_bridge_v2:root_cfg_key(), key(), key()) ->
|
||||||
emqx_rpc:erpc_multicall(ok).
|
emqx_rpc:erpc_multicall(ok).
|
||||||
v2_start_bridge_to_all_nodes_v6(Nodes, ConfRootKey, BridgeType, BridgeName) ->
|
v2_start_bridge_on_all_nodes_v6(Nodes, ConfRootKey, BridgeType, BridgeName) ->
|
||||||
erpc:multicall(
|
erpc:multicall(
|
||||||
Nodes,
|
Nodes,
|
||||||
emqx_bridge_v2,
|
emqx_bridge_v2,
|
||||||
|
@ -184,9 +184,9 @@ v2_start_bridge_to_all_nodes_v6(Nodes, ConfRootKey, BridgeType, BridgeName) ->
|
||||||
?TIMEOUT
|
?TIMEOUT
|
||||||
).
|
).
|
||||||
|
|
||||||
-spec v2_start_bridge_to_node_v6(node(), emqx_bridge_v2:root_cfg_key(), key(), key()) ->
|
-spec v2_start_bridge_on_node_v6(node(), emqx_bridge_v2:root_cfg_key(), key(), key()) ->
|
||||||
term().
|
term().
|
||||||
v2_start_bridge_to_node_v6(Node, ConfRootKey, BridgeType, BridgeName) ->
|
v2_start_bridge_on_node_v6(Node, ConfRootKey, BridgeType, BridgeName) ->
|
||||||
rpc:call(
|
rpc:call(
|
||||||
Node,
|
Node,
|
||||||
emqx_bridge_v2,
|
emqx_bridge_v2,
|
||||||
|
|
|
@ -28,21 +28,31 @@
|
||||||
-export([roots/0, fields/1, desc/1, namespace/0, tags/0]).
|
-export([roots/0, fields/1, desc/1, namespace/0, tags/0]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
get_response/0,
|
actions_get_response/0,
|
||||||
put_request/0,
|
actions_put_request/0,
|
||||||
post_request/0,
|
actions_post_request/0,
|
||||||
examples/1,
|
actions_examples/1,
|
||||||
action_values/4
|
action_values/4
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
sources_get_response/0,
|
||||||
|
sources_put_request/0,
|
||||||
|
sources_post_request/0,
|
||||||
|
sources_examples/1,
|
||||||
|
source_values/4
|
||||||
|
]).
|
||||||
|
|
||||||
%% Exported for mocking
|
%% Exported for mocking
|
||||||
%% TODO: refactor emqx_bridge_v1_compatibility_layer_SUITE so we don't need to
|
%% TODO: refactor emqx_bridge_v1_compatibility_layer_SUITE so we don't need to
|
||||||
%% export this
|
%% export this
|
||||||
-export([
|
-export([
|
||||||
registered_api_schemas/1
|
registered_actions_api_schemas/1,
|
||||||
|
registered_sources_api_schemas/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-export([types/0, types_sc/0]).
|
-export([action_types/0, action_types_sc/0]).
|
||||||
|
-export([source_types/0, source_types_sc/0]).
|
||||||
-export([resource_opts_fields/0, resource_opts_fields/1]).
|
-export([resource_opts_fields/0, resource_opts_fields/1]).
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
|
@ -58,33 +68,140 @@
|
||||||
|
|
||||||
-export([actions_convert_from_connectors/1]).
|
-export([actions_convert_from_connectors/1]).
|
||||||
|
|
||||||
-export_type([action_type/0]).
|
-export_type([action_type/0, source_type/0]).
|
||||||
|
|
||||||
%% Should we explicitly list them here so dialyzer may be more helpful?
|
%% Should we explicitly list them here so dialyzer may be more helpful?
|
||||||
-type action_type() :: atom().
|
-type action_type() :: atom().
|
||||||
|
-type source_type() :: atom().
|
||||||
|
-type http_method() :: get | post | put.
|
||||||
|
-type schema_example_map() :: #{atom() => term()}.
|
||||||
|
|
||||||
%%======================================================================================
|
%%======================================================================================
|
||||||
%% For HTTP APIs
|
%% For HTTP APIs
|
||||||
get_response() ->
|
%%======================================================================================
|
||||||
api_schema("get").
|
|
||||||
|
|
||||||
put_request() ->
|
%%---------------------------------------------
|
||||||
api_schema("put").
|
%% Actions
|
||||||
|
%%---------------------------------------------
|
||||||
|
|
||||||
post_request() ->
|
actions_get_response() ->
|
||||||
api_schema("post").
|
actions_api_schema("get").
|
||||||
|
|
||||||
api_schema(Method) ->
|
actions_put_request() ->
|
||||||
APISchemas = ?MODULE:registered_api_schemas(Method),
|
actions_api_schema("put").
|
||||||
|
|
||||||
|
actions_post_request() ->
|
||||||
|
actions_api_schema("post").
|
||||||
|
|
||||||
|
actions_api_schema(Method) ->
|
||||||
|
APISchemas = ?MODULE:registered_actions_api_schemas(Method),
|
||||||
hoconsc:union(bridge_api_union(APISchemas)).
|
hoconsc:union(bridge_api_union(APISchemas)).
|
||||||
|
|
||||||
registered_api_schemas(Method) ->
|
registered_actions_api_schemas(Method) ->
|
||||||
RegisteredSchemas = emqx_action_info:registered_schema_modules_actions(),
|
RegisteredSchemas = emqx_action_info:registered_schema_modules_actions(),
|
||||||
[
|
[
|
||||||
api_ref(SchemaModule, atom_to_binary(BridgeV2Type), Method ++ "_bridge_v2")
|
api_ref(SchemaModule, atom_to_binary(BridgeV2Type), Method ++ "_bridge_v2")
|
||||||
|| {BridgeV2Type, SchemaModule} <- RegisteredSchemas
|
|| {BridgeV2Type, SchemaModule} <- RegisteredSchemas
|
||||||
].
|
].
|
||||||
|
|
||||||
|
-spec action_values(http_method(), atom(), atom(), schema_example_map()) -> schema_example_map().
|
||||||
|
action_values(Method, ActionType, ConnectorType, ActionValues) ->
|
||||||
|
ActionTypeBin = atom_to_binary(ActionType),
|
||||||
|
ConnectorTypeBin = atom_to_binary(ConnectorType),
|
||||||
|
lists:foldl(
|
||||||
|
fun(M1, M2) ->
|
||||||
|
maps:merge(M1, M2)
|
||||||
|
end,
|
||||||
|
#{
|
||||||
|
enable => true,
|
||||||
|
description => <<"My example ", ActionTypeBin/binary, " action">>,
|
||||||
|
connector => <<ConnectorTypeBin/binary, "_connector">>,
|
||||||
|
resource_opts => #{
|
||||||
|
health_check_interval => "30s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
ActionValues,
|
||||||
|
method_values(action, Method, ActionType)
|
||||||
|
]
|
||||||
|
).
|
||||||
|
|
||||||
|
actions_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,
|
||||||
|
SchemaModules = [Mod || {_, Mod} <- emqx_action_info:registered_schema_modules_actions()],
|
||||||
|
lists:foldl(Fun, #{}, SchemaModules).
|
||||||
|
|
||||||
|
%%---------------------------------------------
|
||||||
|
%% Sources
|
||||||
|
%%---------------------------------------------
|
||||||
|
|
||||||
|
sources_get_response() ->
|
||||||
|
sources_api_schema("get").
|
||||||
|
|
||||||
|
sources_put_request() ->
|
||||||
|
sources_api_schema("put").
|
||||||
|
|
||||||
|
sources_post_request() ->
|
||||||
|
sources_api_schema("post").
|
||||||
|
|
||||||
|
sources_api_schema(Method) ->
|
||||||
|
APISchemas = ?MODULE:registered_sources_api_schemas(Method),
|
||||||
|
hoconsc:union(bridge_api_union(APISchemas)).
|
||||||
|
|
||||||
|
registered_sources_api_schemas(Method) ->
|
||||||
|
RegisteredSchemas = emqx_action_info:registered_schema_modules_sources(),
|
||||||
|
[
|
||||||
|
api_ref(SchemaModule, atom_to_binary(BridgeV2Type), Method ++ "_source")
|
||||||
|
|| {BridgeV2Type, SchemaModule} <- RegisteredSchemas
|
||||||
|
].
|
||||||
|
|
||||||
|
-spec source_values(http_method(), atom(), atom(), schema_example_map()) -> schema_example_map().
|
||||||
|
source_values(Method, SourceType, ConnectorType, SourceValues) ->
|
||||||
|
SourceTypeBin = atom_to_binary(SourceType),
|
||||||
|
ConnectorTypeBin = atom_to_binary(ConnectorType),
|
||||||
|
lists:foldl(
|
||||||
|
fun(M1, M2) ->
|
||||||
|
maps:merge(M1, M2)
|
||||||
|
end,
|
||||||
|
#{
|
||||||
|
enable => true,
|
||||||
|
description => <<"My example ", SourceTypeBin/binary, " source">>,
|
||||||
|
connector => <<ConnectorTypeBin/binary, "_connector">>,
|
||||||
|
resource_opts => #{
|
||||||
|
health_check_interval => "30s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
SourceValues,
|
||||||
|
method_values(source, Method, SourceType)
|
||||||
|
]
|
||||||
|
).
|
||||||
|
|
||||||
|
sources_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,
|
||||||
|
SchemaModules = [Mod || {_, Mod} <- emqx_action_info:registered_schema_modules_sources()],
|
||||||
|
lists:foldl(Fun, #{}, SchemaModules).
|
||||||
|
|
||||||
|
%%---------------------------------------------
|
||||||
|
%% Common helpers
|
||||||
|
%%---------------------------------------------
|
||||||
|
|
||||||
api_ref(Module, Type, Method) ->
|
api_ref(Module, Type, Method) ->
|
||||||
{Type, ref(Module, Method)}.
|
{Type, ref(Module, Method)}.
|
||||||
|
|
||||||
|
@ -111,41 +228,17 @@ bridge_api_union(Refs) ->
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-type http_method() :: get | post | put.
|
-spec method_values(action | source, http_method(), atom()) -> schema_example_map().
|
||||||
-type schema_example_map() :: #{atom() => term()}.
|
method_values(Kind, post, Type) ->
|
||||||
|
KindBin = atom_to_binary(Kind),
|
||||||
-spec action_values(http_method(), atom(), atom(), schema_example_map()) -> schema_example_map().
|
|
||||||
action_values(Method, ActionType, ConnectorType, ActionValues) ->
|
|
||||||
ActionTypeBin = atom_to_binary(ActionType),
|
|
||||||
ConnectorTypeBin = atom_to_binary(ConnectorType),
|
|
||||||
lists:foldl(
|
|
||||||
fun(M1, M2) ->
|
|
||||||
maps:merge(M1, M2)
|
|
||||||
end,
|
|
||||||
#{
|
|
||||||
enable => true,
|
|
||||||
description => <<"My example ", ActionTypeBin/binary, " action">>,
|
|
||||||
connector => <<ConnectorTypeBin/binary, "_connector">>,
|
|
||||||
resource_opts => #{
|
|
||||||
health_check_interval => "30s"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
ActionValues,
|
|
||||||
method_values(Method, ActionType)
|
|
||||||
]
|
|
||||||
).
|
|
||||||
|
|
||||||
-spec method_values(http_method(), atom()) -> schema_example_map().
|
|
||||||
method_values(post, Type) ->
|
|
||||||
TypeBin = atom_to_binary(Type),
|
TypeBin = atom_to_binary(Type),
|
||||||
#{
|
#{
|
||||||
name => <<TypeBin/binary, "_action">>,
|
name => <<TypeBin/binary, "_", KindBin/binary>>,
|
||||||
type => TypeBin
|
type => TypeBin
|
||||||
};
|
};
|
||||||
method_values(get, Type) ->
|
method_values(Kind, get, Type) ->
|
||||||
maps:merge(
|
maps:merge(
|
||||||
method_values(post, Type),
|
method_values(Kind, post, Type),
|
||||||
#{
|
#{
|
||||||
status => <<"connected">>,
|
status => <<"connected">>,
|
||||||
node_status => [
|
node_status => [
|
||||||
|
@ -156,7 +249,7 @@ method_values(get, Type) ->
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
method_values(put, _Type) ->
|
method_values(_Kind, put, _Type) ->
|
||||||
#{}.
|
#{}.
|
||||||
|
|
||||||
api_fields("get_bridge_v2", Type, Fields) ->
|
api_fields("get_bridge_v2", Type, Fields) ->
|
||||||
|
@ -175,16 +268,33 @@ api_fields("post_bridge_v2", Type, Fields) ->
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
api_fields("put_bridge_v2", _Type, Fields) ->
|
api_fields("put_bridge_v2", _Type, Fields) ->
|
||||||
|
Fields;
|
||||||
|
api_fields("get_source", Type, Fields) ->
|
||||||
|
lists:append(
|
||||||
|
[
|
||||||
|
emqx_bridge_schema:type_and_name_fields(Type),
|
||||||
|
emqx_bridge_schema:status_fields(),
|
||||||
|
Fields
|
||||||
|
]
|
||||||
|
);
|
||||||
|
api_fields("post_source", Type, Fields) ->
|
||||||
|
lists:append(
|
||||||
|
[
|
||||||
|
emqx_bridge_schema:type_and_name_fields(Type),
|
||||||
|
Fields
|
||||||
|
]
|
||||||
|
);
|
||||||
|
api_fields("put_source", _Type, Fields) ->
|
||||||
Fields.
|
Fields.
|
||||||
|
|
||||||
%%======================================================================================
|
%%======================================================================================
|
||||||
%% HOCON Schema Callbacks
|
%% HOCON Schema Callbacks
|
||||||
%%======================================================================================
|
%%======================================================================================
|
||||||
|
|
||||||
namespace() -> "actions".
|
namespace() -> "actions_and_sources".
|
||||||
|
|
||||||
tags() ->
|
tags() ->
|
||||||
[<<"Actions">>].
|
[<<"Actions">>, <<"Sources">>].
|
||||||
|
|
||||||
-dialyzer({nowarn_function, roots/0}).
|
-dialyzer({nowarn_function, roots/0}).
|
||||||
|
|
||||||
|
@ -231,13 +341,21 @@ desc(resource_opts) ->
|
||||||
desc(_) ->
|
desc(_) ->
|
||||||
undefined.
|
undefined.
|
||||||
|
|
||||||
-spec types() -> [action_type()].
|
-spec action_types() -> [action_type()].
|
||||||
types() ->
|
action_types() ->
|
||||||
proplists:get_keys(?MODULE:fields(actions)).
|
proplists:get_keys(?MODULE:fields(actions)).
|
||||||
|
|
||||||
-spec types_sc() -> ?ENUM([action_type()]).
|
-spec action_types_sc() -> ?ENUM([action_type()]).
|
||||||
types_sc() ->
|
action_types_sc() ->
|
||||||
hoconsc:enum(types()).
|
hoconsc:enum(action_types()).
|
||||||
|
|
||||||
|
-spec source_types() -> [source_type()].
|
||||||
|
source_types() ->
|
||||||
|
proplists:get_keys(?MODULE:fields(sources)).
|
||||||
|
|
||||||
|
-spec source_types_sc() -> ?ENUM([source_type()]).
|
||||||
|
source_types_sc() ->
|
||||||
|
hoconsc:enum(source_types()).
|
||||||
|
|
||||||
resource_opts_fields() ->
|
resource_opts_fields() ->
|
||||||
resource_opts_fields(_Overrides = []).
|
resource_opts_fields(_Overrides = []).
|
||||||
|
@ -268,19 +386,6 @@ resource_opts_fields(Overrides) ->
|
||||||
emqx_resource_schema:create_opts(Overrides)
|
emqx_resource_schema:create_opts(Overrides)
|
||||||
).
|
).
|
||||||
|
|
||||||
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,
|
|
||||||
SchemaModules = [Mod || {_, Mod} <- emqx_action_info:registered_schema_modules_actions()],
|
|
||||||
lists:foldl(Fun, #{}, SchemaModules).
|
|
||||||
|
|
||||||
top_level_common_action_keys() ->
|
top_level_common_action_keys() ->
|
||||||
[
|
[
|
||||||
<<"connector">>,
|
<<"connector">>,
|
||||||
|
|
|
@ -104,7 +104,7 @@ setup_mocks() ->
|
||||||
catch meck:new(emqx_bridge_v2_schema, MeckOpts),
|
catch meck:new(emqx_bridge_v2_schema, MeckOpts),
|
||||||
meck:expect(
|
meck:expect(
|
||||||
emqx_bridge_v2_schema,
|
emqx_bridge_v2_schema,
|
||||||
registered_api_schemas,
|
registered_actions_api_schemas,
|
||||||
1,
|
1,
|
||||||
fun(Method) ->
|
fun(Method) ->
|
||||||
[{bridge_type_bin(), hoconsc:ref(?MODULE, "api_v2_" ++ Method)}]
|
[{bridge_type_bin(), hoconsc:ref(?MODULE, "api_v2_" ++ Method)}]
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
|
|
||||||
-import(emqx_common_test_helpers, [on_exit/1]).
|
-import(emqx_common_test_helpers, [on_exit/1]).
|
||||||
|
|
||||||
|
-define(ROOT_KEY_ACTIONS, actions).
|
||||||
|
-define(ROOT_KEY_SOURCES, sources).
|
||||||
|
|
||||||
%% ct setup helpers
|
%% ct setup helpers
|
||||||
|
|
||||||
init_per_suite(Config, Apps) ->
|
init_per_suite(Config, Apps) ->
|
||||||
|
@ -152,6 +155,49 @@ create_bridge(Config, Overrides) ->
|
||||||
ct:pal("creating bridge with config: ~p", [BridgeConfig]),
|
ct:pal("creating bridge with config: ~p", [BridgeConfig]),
|
||||||
emqx_bridge_v2:create(BridgeType, BridgeName, BridgeConfig).
|
emqx_bridge_v2:create(BridgeType, BridgeName, BridgeConfig).
|
||||||
|
|
||||||
|
get_ct_config_with_fallback(Config, [Key]) ->
|
||||||
|
?config(Key, Config);
|
||||||
|
get_ct_config_with_fallback(Config, [Key | Rest]) ->
|
||||||
|
case ?config(Key, Config) of
|
||||||
|
undefined ->
|
||||||
|
get_ct_config_with_fallback(Config, Rest);
|
||||||
|
X ->
|
||||||
|
X
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_config_by_kind(Config, Overrides) ->
|
||||||
|
Kind = ?config(bridge_kind, Config),
|
||||||
|
get_config_by_kind(Kind, Config, Overrides).
|
||||||
|
|
||||||
|
get_config_by_kind(Kind, Config, Overrides) ->
|
||||||
|
case Kind of
|
||||||
|
action ->
|
||||||
|
%% TODO: refactor tests to use action_type...
|
||||||
|
ActionType = get_ct_config_with_fallback(Config, [action_type, bridge_type]),
|
||||||
|
ActionName = get_ct_config_with_fallback(Config, [action_name, bridge_name]),
|
||||||
|
ActionConfig0 = get_ct_config_with_fallback(Config, [action_config, bridge_config]),
|
||||||
|
ActionConfig = emqx_utils_maps:deep_merge(ActionConfig0, Overrides),
|
||||||
|
#{type => ActionType, name => ActionName, config => ActionConfig};
|
||||||
|
source ->
|
||||||
|
SourceType = ?config(source_type, Config),
|
||||||
|
SourceName = ?config(source_name, Config),
|
||||||
|
SourceConfig0 = ?config(source_config, Config),
|
||||||
|
SourceConfig = emqx_utils_maps:deep_merge(SourceConfig0, Overrides),
|
||||||
|
#{type => SourceType, name => SourceName, config => SourceConfig}
|
||||||
|
end.
|
||||||
|
|
||||||
|
api_path_root(Kind) ->
|
||||||
|
case Kind of
|
||||||
|
action -> "actions";
|
||||||
|
source -> "sources"
|
||||||
|
end.
|
||||||
|
|
||||||
|
conf_root_key(Kind) ->
|
||||||
|
case Kind of
|
||||||
|
action -> ?ROOT_KEY_ACTIONS;
|
||||||
|
source -> ?ROOT_KEY_SOURCES
|
||||||
|
end.
|
||||||
|
|
||||||
maybe_json_decode(X) ->
|
maybe_json_decode(X) ->
|
||||||
case emqx_utils_json:safe_decode(X, [return_maps]) of
|
case emqx_utils_json:safe_decode(X, [return_maps]) of
|
||||||
{ok, Decoded} -> Decoded;
|
{ok, Decoded} -> Decoded;
|
||||||
|
@ -218,26 +264,26 @@ create_bridge_api(Config) ->
|
||||||
create_bridge_api(Config, _Overrides = #{}).
|
create_bridge_api(Config, _Overrides = #{}).
|
||||||
|
|
||||||
create_bridge_api(Config, Overrides) ->
|
create_bridge_api(Config, Overrides) ->
|
||||||
BridgeType = ?config(bridge_type, Config),
|
|
||||||
BridgeName = ?config(bridge_name, Config),
|
|
||||||
BridgeConfig0 = ?config(bridge_config, Config),
|
|
||||||
BridgeConfig = emqx_utils_maps:deep_merge(BridgeConfig0, Overrides),
|
|
||||||
|
|
||||||
{ok, {{_, 201, _}, _, _}} = create_connector_api(Config),
|
{ok, {{_, 201, _}, _, _}} = create_connector_api(Config),
|
||||||
|
create_kind_api(Config, Overrides).
|
||||||
|
|
||||||
Params = BridgeConfig#{<<"type">> => BridgeType, <<"name">> => BridgeName},
|
create_kind_api(Config) ->
|
||||||
Path = emqx_mgmt_api_test_util:api_path(["actions"]),
|
create_kind_api(Config, _Overrides = #{}).
|
||||||
AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
|
|
||||||
Opts = #{return_all => true},
|
create_kind_api(Config, Overrides) ->
|
||||||
ct:pal("creating bridge (via http): ~p", [Params]),
|
Kind = proplists:get_value(bridge_kind, Config, action),
|
||||||
Res =
|
#{
|
||||||
case emqx_mgmt_api_test_util:request_api(post, Path, "", AuthHeader, Params, Opts) of
|
type := Type,
|
||||||
{ok, {Status, Headers, Body0}} ->
|
name := Name,
|
||||||
{ok, {Status, Headers, emqx_utils_json:decode(Body0, [return_maps])}};
|
config := BridgeConfig
|
||||||
Error ->
|
} = get_config_by_kind(Kind, Config, Overrides),
|
||||||
Error
|
Params = BridgeConfig#{<<"type">> => Type, <<"name">> => Name},
|
||||||
end,
|
PathRoot = api_path_root(Kind),
|
||||||
ct:pal("bridge create result: ~p", [Res]),
|
Path = emqx_mgmt_api_test_util:api_path([PathRoot]),
|
||||||
|
ct:pal("creating bridge (~s, http):\n ~p", [Kind, Params]),
|
||||||
|
Method = post,
|
||||||
|
Res = request(Method, Path, Params),
|
||||||
|
ct:pal("bridge create (~s, http) result:\n ~p", [Kind, Res]),
|
||||||
Res.
|
Res.
|
||||||
|
|
||||||
create_connector_api(Config) ->
|
create_connector_api(Config) ->
|
||||||
|
@ -288,27 +334,29 @@ update_bridge_api(Config) ->
|
||||||
update_bridge_api(Config, _Overrides = #{}).
|
update_bridge_api(Config, _Overrides = #{}).
|
||||||
|
|
||||||
update_bridge_api(Config, Overrides) ->
|
update_bridge_api(Config, Overrides) ->
|
||||||
BridgeType = ?config(bridge_type, Config),
|
Kind = proplists:get_value(bridge_kind, Config, action),
|
||||||
Name = ?config(bridge_name, Config),
|
#{
|
||||||
BridgeConfig0 = ?config(bridge_config, Config),
|
type := Type,
|
||||||
BridgeConfig = emqx_utils_maps:deep_merge(BridgeConfig0, Overrides),
|
name := Name,
|
||||||
BridgeId = emqx_bridge_resource:bridge_id(BridgeType, Name),
|
config := Params
|
||||||
Path = emqx_mgmt_api_test_util:api_path(["actions", BridgeId]),
|
} = get_config_by_kind(Kind, Config, Overrides),
|
||||||
AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
|
BridgeId = emqx_bridge_resource:bridge_id(Type, Name),
|
||||||
Opts = #{return_all => true},
|
PathRoot = api_path_root(Kind),
|
||||||
ct:pal("updating bridge (via http): ~p", [BridgeConfig]),
|
Path = emqx_mgmt_api_test_util:api_path([PathRoot, BridgeId]),
|
||||||
Res =
|
ct:pal("updating bridge (~s, http):\n ~p", [Kind, Params]),
|
||||||
case emqx_mgmt_api_test_util:request_api(put, Path, "", AuthHeader, BridgeConfig, Opts) of
|
Method = put,
|
||||||
{ok, {_Status, _Headers, Body0}} -> {ok, emqx_utils_json:decode(Body0, [return_maps])};
|
Res = request(Method, Path, Params),
|
||||||
Error -> Error
|
ct:pal("update bridge (~s, http) result:\n ~p", [Kind, Res]),
|
||||||
end,
|
|
||||||
ct:pal("bridge update result: ~p", [Res]),
|
|
||||||
Res.
|
Res.
|
||||||
|
|
||||||
op_bridge_api(Op, BridgeType, BridgeName) ->
|
op_bridge_api(Op, BridgeType, BridgeName) ->
|
||||||
|
op_bridge_api(_Kind = action, Op, BridgeType, BridgeName).
|
||||||
|
|
||||||
|
op_bridge_api(Kind, Op, BridgeType, BridgeName) ->
|
||||||
BridgeId = emqx_bridge_resource:bridge_id(BridgeType, BridgeName),
|
BridgeId = emqx_bridge_resource:bridge_id(BridgeType, BridgeName),
|
||||||
Path = emqx_mgmt_api_test_util:api_path(["actions", BridgeId, Op]),
|
PathRoot = api_path_root(Kind),
|
||||||
ct:pal("calling bridge ~p (via http): ~p", [BridgeId, Op]),
|
Path = emqx_mgmt_api_test_util:api_path([PathRoot, BridgeId, Op]),
|
||||||
|
ct:pal("calling bridge ~p (~s, http):\n ~p", [BridgeId, Kind, Op]),
|
||||||
Method = post,
|
Method = post,
|
||||||
Params = [],
|
Params = [],
|
||||||
Res = request(Method, Path, Params),
|
Res = request(Method, Path, Params),
|
||||||
|
@ -326,17 +374,16 @@ probe_bridge_api(Config, Overrides) ->
|
||||||
probe_bridge_api(BridgeType, BridgeName, BridgeConfig).
|
probe_bridge_api(BridgeType, BridgeName, BridgeConfig).
|
||||||
|
|
||||||
probe_bridge_api(BridgeType, BridgeName, BridgeConfig) ->
|
probe_bridge_api(BridgeType, BridgeName, BridgeConfig) ->
|
||||||
|
probe_bridge_api(action, BridgeType, BridgeName, BridgeConfig).
|
||||||
|
|
||||||
|
probe_bridge_api(Kind, BridgeType, BridgeName, BridgeConfig) ->
|
||||||
Params = BridgeConfig#{<<"type">> => BridgeType, <<"name">> => BridgeName},
|
Params = BridgeConfig#{<<"type">> => BridgeType, <<"name">> => BridgeName},
|
||||||
Path = emqx_mgmt_api_test_util:api_path(["actions_probe"]),
|
PathRoot = api_path_root(Kind),
|
||||||
AuthHeader = emqx_mgmt_api_test_util:auth_header_(),
|
Path = emqx_mgmt_api_test_util:api_path([PathRoot ++ "_probe"]),
|
||||||
Opts = #{return_all => true},
|
ct:pal("probing bridge (~s, http):\n ~p", [Kind, Params]),
|
||||||
ct:pal("probing bridge (via http): ~p", [Params]),
|
Method = post,
|
||||||
Res =
|
Res = request(Method, Path, Params),
|
||||||
case emqx_mgmt_api_test_util:request_api(post, Path, "", AuthHeader, Params, Opts) of
|
ct:pal("bridge probe (~s, http) result:\n ~p", [Kind, Res]),
|
||||||
{ok, {{_, 204, _}, _Headers, _Body0} = Res0} -> {ok, Res0};
|
|
||||||
Error -> Error
|
|
||||||
end,
|
|
||||||
ct:pal("bridge probe result: ~p", [Res]),
|
|
||||||
Res.
|
Res.
|
||||||
|
|
||||||
list_bridges_http_api_v1() ->
|
list_bridges_http_api_v1() ->
|
||||||
|
@ -353,6 +400,13 @@ list_actions_http_api() ->
|
||||||
ct:pal("list actions (http v2) result:\n ~p", [Res]),
|
ct:pal("list actions (http v2) result:\n ~p", [Res]),
|
||||||
Res.
|
Res.
|
||||||
|
|
||||||
|
list_sources_http_api() ->
|
||||||
|
Path = emqx_mgmt_api_test_util:api_path(["sources"]),
|
||||||
|
ct:pal("list sources (http v2)"),
|
||||||
|
Res = request(get, Path, _Params = []),
|
||||||
|
ct:pal("list sources (http v2) result:\n ~p", [Res]),
|
||||||
|
Res.
|
||||||
|
|
||||||
list_connectors_http_api() ->
|
list_connectors_http_api() ->
|
||||||
Path = emqx_mgmt_api_test_util:api_path(["connectors"]),
|
Path = emqx_mgmt_api_test_util:api_path(["connectors"]),
|
||||||
ct:pal("list connectors"),
|
ct:pal("list connectors"),
|
||||||
|
@ -506,13 +560,6 @@ t_create_via_http(Config) ->
|
||||||
begin
|
begin
|
||||||
?assertMatch({ok, _}, create_bridge_api(Config)),
|
?assertMatch({ok, _}, create_bridge_api(Config)),
|
||||||
|
|
||||||
%% lightweight matrix testing some configs
|
|
||||||
?assertMatch(
|
|
||||||
{ok, _},
|
|
||||||
update_bridge_api(
|
|
||||||
Config
|
|
||||||
)
|
|
||||||
),
|
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{ok, _},
|
{ok, _},
|
||||||
update_bridge_api(
|
update_bridge_api(
|
||||||
|
@ -526,23 +573,26 @@ t_create_via_http(Config) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_start_stop(Config, StopTracePoint) ->
|
t_start_stop(Config, StopTracePoint) ->
|
||||||
BridgeType = ?config(bridge_type, Config),
|
Kind = proplists:get_value(bridge_kind, Config, action),
|
||||||
BridgeName = ?config(bridge_name, Config),
|
|
||||||
BridgeConfig = ?config(bridge_config, Config),
|
|
||||||
ConnectorName = ?config(connector_name, Config),
|
ConnectorName = ?config(connector_name, Config),
|
||||||
ConnectorType = ?config(connector_type, Config),
|
ConnectorType = ?config(connector_type, Config),
|
||||||
ConnectorConfig = ?config(connector_config, Config),
|
#{
|
||||||
|
type := Type,
|
||||||
|
name := Name,
|
||||||
|
config := BridgeConfig
|
||||||
|
} = get_config_by_kind(Kind, Config, _Overrides = #{}),
|
||||||
|
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{ok, _},
|
{ok, {{_, 201, _}, _, _}},
|
||||||
emqx_connector:create(ConnectorType, ConnectorName, ConnectorConfig)
|
create_connector_api(Config)
|
||||||
),
|
),
|
||||||
|
|
||||||
?check_trace(
|
?check_trace(
|
||||||
begin
|
begin
|
||||||
ProbeRes0 = probe_bridge_api(
|
ProbeRes0 = probe_bridge_api(
|
||||||
BridgeType,
|
Kind,
|
||||||
BridgeName,
|
Type,
|
||||||
|
Name,
|
||||||
BridgeConfig
|
BridgeConfig
|
||||||
),
|
),
|
||||||
?assertMatch({ok, {{_, 204, _}, _Headers, _Body}}, ProbeRes0),
|
?assertMatch({ok, {{_, 204, _}, _Headers, _Body}}, ProbeRes0),
|
||||||
|
@ -550,8 +600,9 @@ t_start_stop(Config, StopTracePoint) ->
|
||||||
AtomsBefore = erlang:system_info(atom_count),
|
AtomsBefore = erlang:system_info(atom_count),
|
||||||
%% Probe again; shouldn't have created more atoms.
|
%% Probe again; shouldn't have created more atoms.
|
||||||
ProbeRes1 = probe_bridge_api(
|
ProbeRes1 = probe_bridge_api(
|
||||||
BridgeType,
|
Kind,
|
||||||
BridgeName,
|
Type,
|
||||||
|
Name,
|
||||||
BridgeConfig
|
BridgeConfig
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -559,9 +610,9 @@ t_start_stop(Config, StopTracePoint) ->
|
||||||
AtomsAfter = erlang:system_info(atom_count),
|
AtomsAfter = erlang:system_info(atom_count),
|
||||||
?assertEqual(AtomsBefore, AtomsAfter),
|
?assertEqual(AtomsBefore, AtomsAfter),
|
||||||
|
|
||||||
?assertMatch({ok, _}, emqx_bridge_v2:create(BridgeType, BridgeName, BridgeConfig)),
|
?assertMatch({ok, _}, create_kind_api(Config)),
|
||||||
|
|
||||||
ResourceId = emqx_bridge_resource:resource_id(BridgeType, BridgeName),
|
ResourceId = emqx_bridge_resource:resource_id(conf_root_key(Kind), Type, Name),
|
||||||
|
|
||||||
%% Since the connection process is async, we give it some time to
|
%% Since the connection process is async, we give it some time to
|
||||||
%% stabilize and avoid flakiness.
|
%% stabilize and avoid flakiness.
|
||||||
|
@ -574,7 +625,7 @@ t_start_stop(Config, StopTracePoint) ->
|
||||||
%% `start` bridge to trigger `already_started`
|
%% `start` bridge to trigger `already_started`
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
{ok, {{_, 204, _}, _Headers, []}},
|
{ok, {{_, 204, _}, _Headers, []}},
|
||||||
emqx_bridge_v2_testlib:op_bridge_api("start", BridgeType, BridgeName)
|
op_bridge_api(Kind, "start", Type, Name)
|
||||||
),
|
),
|
||||||
|
|
||||||
?assertEqual({ok, connected}, emqx_resource_manager:health_check(ResourceId)),
|
?assertEqual({ok, connected}, emqx_resource_manager:health_check(ResourceId)),
|
||||||
|
@ -624,10 +675,10 @@ t_start_stop(Config, StopTracePoint) ->
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
ok
|
#{resource_id => ResourceId}
|
||||||
end,
|
end,
|
||||||
fun(Trace) ->
|
fun(Res, Trace) ->
|
||||||
ResourceId = emqx_bridge_resource:resource_id(BridgeType, BridgeName),
|
#{resource_id := ResourceId} = Res,
|
||||||
%% one for each probe, one for real
|
%% one for each probe, one for real
|
||||||
?assertMatch(
|
?assertMatch(
|
||||||
[_, _, #{instance_id := ResourceId}],
|
[_, _, #{instance_id := ResourceId}],
|
||||||
|
|
|
@ -108,7 +108,7 @@ connector_resource_opts_test() ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
actions_api_spec_post_fields_test() ->
|
actions_api_spec_post_fields_test() ->
|
||||||
?UNION(Union) = emqx_bridge_v2_schema:post_request(),
|
?UNION(Union) = emqx_bridge_v2_schema:actions_post_request(),
|
||||||
Schemas =
|
Schemas =
|
||||||
lists:map(
|
lists:map(
|
||||||
fun(?R_REF(SchemaMod, StructName)) ->
|
fun(?R_REF(SchemaMod, StructName)) ->
|
||||||
|
|
|
@ -237,7 +237,9 @@ on_stop(ResourceId, State) ->
|
||||||
ets:delete(TopicToHandlerIndex)
|
ets:delete(TopicToHandlerIndex)
|
||||||
end,
|
end,
|
||||||
Allocated = emqx_resource:get_allocated_resources(ResourceId),
|
Allocated = emqx_resource:get_allocated_resources(ResourceId),
|
||||||
ok = stop_helper(Allocated).
|
ok = stop_helper(Allocated),
|
||||||
|
?tp(mqtt_connector_stopped, #{instance_id => ResourceId}),
|
||||||
|
ok.
|
||||||
|
|
||||||
stop_helper(#{pool_name := PoolName}) ->
|
stop_helper(#{pool_name := PoolName}) ->
|
||||||
emqx_resource_pool:stop(PoolName).
|
emqx_resource_pool:stop(PoolName).
|
||||||
|
|
|
@ -27,6 +27,9 @@
|
||||||
conn_bridge_examples/1
|
conn_bridge_examples/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-define(ACTION_TYPE, mqtt).
|
||||||
|
-define(SOURCE_TYPE, mqtt).
|
||||||
|
|
||||||
%%======================================================================================
|
%%======================================================================================
|
||||||
%% Hocon Schema Definitions
|
%% Hocon Schema Definitions
|
||||||
namespace() -> "bridge_mqtt_publisher".
|
namespace() -> "bridge_mqtt_publisher".
|
||||||
|
@ -86,14 +89,18 @@ fields(action_resource_opts) ->
|
||||||
fun({K, _V}) -> not lists:member(K, UnsupportedOpts) end,
|
fun({K, _V}) -> not lists:member(K, UnsupportedOpts) end,
|
||||||
emqx_bridge_v2_schema:resource_opts_fields()
|
emqx_bridge_v2_schema:resource_opts_fields()
|
||||||
);
|
);
|
||||||
fields("get_connector") ->
|
fields(Field) when
|
||||||
emqx_bridge_mqtt_connector_schema:fields("config_connector");
|
Field == "get_bridge_v2";
|
||||||
fields("get_bridge_v2") ->
|
Field == "post_bridge_v2";
|
||||||
fields("mqtt_publisher_action");
|
Field == "put_bridge_v2"
|
||||||
fields("post_bridge_v2") ->
|
->
|
||||||
fields("mqtt_publisher_action") ++ emqx_bridge_schema:type_and_name_fields(mqtt);
|
emqx_bridge_v2_schema:api_fields(Field, ?ACTION_TYPE, fields("mqtt_publisher_action"));
|
||||||
fields("put_bridge_v2") ->
|
fields(Field) when
|
||||||
fields("mqtt_publisher_action");
|
Field == "get_source";
|
||||||
|
Field == "post_source";
|
||||||
|
Field == "put_source"
|
||||||
|
->
|
||||||
|
emqx_bridge_v2_schema:api_fields(Field, ?SOURCE_TYPE, fields("mqtt_subscriber_source"));
|
||||||
fields(What) ->
|
fields(What) ->
|
||||||
error({emqx_bridge_mqtt_pubsub_schema, missing_field_handler, What}).
|
error({emqx_bridge_mqtt_pubsub_schema, missing_field_handler, What}).
|
||||||
%% v2: api schema
|
%% v2: api schema
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% 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_mqtt_v2_subscriber_SUITE).
|
||||||
|
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
-compile(export_all).
|
||||||
|
|
||||||
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
-include_lib("emqx/include/emqx_hooks.hrl").
|
||||||
|
-include_lib("stdlib/include/assert.hrl").
|
||||||
|
-include_lib("emqx/include/asserts.hrl").
|
||||||
|
-include_lib("common_test/include/ct.hrl").
|
||||||
|
-include_lib("snabbkaffe/include/snabbkaffe.hrl").
|
||||||
|
|
||||||
|
-import(emqx_common_test_helpers, [on_exit/1]).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% CT boilerplate
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
all() ->
|
||||||
|
emqx_common_test_helpers:all(?MODULE).
|
||||||
|
|
||||||
|
init_per_suite(Config) ->
|
||||||
|
Apps = emqx_cth_suite:start(
|
||||||
|
[
|
||||||
|
emqx,
|
||||||
|
emqx_conf,
|
||||||
|
emqx_connector,
|
||||||
|
emqx_bridge_mqtt,
|
||||||
|
emqx_bridge,
|
||||||
|
emqx_rule_engine,
|
||||||
|
emqx_management,
|
||||||
|
{emqx_dashboard, "dashboard.listeners.http { enable = true, bind = 18083 }"}
|
||||||
|
],
|
||||||
|
#{work_dir => emqx_cth_suite:work_dir(Config)}
|
||||||
|
),
|
||||||
|
{ok, Api} = emqx_common_test_http:create_default_app(),
|
||||||
|
[
|
||||||
|
{apps, Apps},
|
||||||
|
{api, Api}
|
||||||
|
| Config
|
||||||
|
].
|
||||||
|
|
||||||
|
end_per_suite(Config) ->
|
||||||
|
Apps = ?config(apps, Config),
|
||||||
|
emqx_cth_suite:stop(Apps),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
init_per_testcase(TestCase, Config) ->
|
||||||
|
UniqueNum = integer_to_binary(erlang:unique_integer()),
|
||||||
|
Name = iolist_to_binary([atom_to_binary(TestCase), UniqueNum]),
|
||||||
|
ConnectorConfig = connector_config(),
|
||||||
|
SourceConfig = source_config(#{connector => Name}),
|
||||||
|
[
|
||||||
|
{bridge_kind, source},
|
||||||
|
{source_type, mqtt},
|
||||||
|
{source_name, Name},
|
||||||
|
{source_config, SourceConfig},
|
||||||
|
{connector_type, mqtt},
|
||||||
|
{connector_name, Name},
|
||||||
|
{connector_config, ConnectorConfig}
|
||||||
|
| Config
|
||||||
|
].
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% Helper fns
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
connector_config() ->
|
||||||
|
%% !!!!!!!!!!!! FIXME!!!!!! add more fields ("server_configs")
|
||||||
|
#{
|
||||||
|
<<"enable">> => true,
|
||||||
|
<<"description">> => <<"my connector">>,
|
||||||
|
<<"pool_size">> => 3,
|
||||||
|
<<"server">> => <<"127.0.0.1:1883">>,
|
||||||
|
<<"resource_opts">> => #{
|
||||||
|
<<"health_check_interval">> => <<"15s">>,
|
||||||
|
<<"start_after_created">> => true,
|
||||||
|
<<"start_timeout">> => <<"5s">>
|
||||||
|
}
|
||||||
|
}.
|
||||||
|
|
||||||
|
source_config(Overrides0) ->
|
||||||
|
Overrides = emqx_utils_maps:binary_key_map(Overrides0),
|
||||||
|
CommonConfig =
|
||||||
|
#{
|
||||||
|
<<"enable">> => true,
|
||||||
|
<<"connector">> => <<"please override">>,
|
||||||
|
<<"parameters">> =>
|
||||||
|
#{
|
||||||
|
<<"remote">> =>
|
||||||
|
#{
|
||||||
|
<<"topic">> => <<"remote/topic">>,
|
||||||
|
<<"qos">> => 2
|
||||||
|
},
|
||||||
|
<<"local">> =>
|
||||||
|
#{
|
||||||
|
<<"topic">> => <<"local/topic">>,
|
||||||
|
<<"qos">> => 2,
|
||||||
|
<<"retain">> => false,
|
||||||
|
<<"payload">> => <<"${payload}">>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
<<"resource_opts">> => #{
|
||||||
|
<<"batch_size">> => 1,
|
||||||
|
<<"batch_time">> => <<"0ms">>,
|
||||||
|
<<"buffer_mode">> => <<"memory_only">>,
|
||||||
|
<<"buffer_seg_bytes">> => <<"10MB">>,
|
||||||
|
<<"health_check_interval">> => <<"15s">>,
|
||||||
|
<<"inflight_window">> => 100,
|
||||||
|
<<"max_buffer_bytes">> => <<"256MB">>,
|
||||||
|
<<"metrics_flush_interval">> => <<"1s">>,
|
||||||
|
<<"query_mode">> => <<"sync">>,
|
||||||
|
<<"request_ttl">> => <<"45s">>,
|
||||||
|
<<"resume_interval">> => <<"15s">>,
|
||||||
|
<<"worker_pool_size">> => <<"1">>
|
||||||
|
}
|
||||||
|
},
|
||||||
|
maps:merge(CommonConfig, Overrides).
|
||||||
|
|
||||||
|
replace(Key, Value, Proplist) ->
|
||||||
|
lists:keyreplace(Key, 1, Proplist, {Key, Value}).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% Testcases
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
t_create_via_http(Config) ->
|
||||||
|
ConnectorName = ?config(connector_name, Config),
|
||||||
|
ok = emqx_bridge_v2_testlib:t_create_via_http(Config),
|
||||||
|
?assertMatch(
|
||||||
|
{ok,
|
||||||
|
{{_, 200, _}, _, [
|
||||||
|
#{
|
||||||
|
<<"enable">> := true,
|
||||||
|
<<"status">> := <<"connected">>
|
||||||
|
}
|
||||||
|
]}},
|
||||||
|
emqx_bridge_v2_testlib:list_bridges_http_api_v1()
|
||||||
|
),
|
||||||
|
NewSourceName = <<"my_other_source">>,
|
||||||
|
{ok, {{_, 201, _}, _, _}} =
|
||||||
|
emqx_bridge_v2_testlib:create_kind_api(
|
||||||
|
replace(source_name, NewSourceName, Config)
|
||||||
|
),
|
||||||
|
?assertMatch(
|
||||||
|
{ok,
|
||||||
|
{{_, 200, _}, _, [
|
||||||
|
#{<<"connector">> := ConnectorName},
|
||||||
|
#{<<"connector">> := ConnectorName}
|
||||||
|
]}},
|
||||||
|
emqx_bridge_v2_testlib:list_sources_http_api()
|
||||||
|
),
|
||||||
|
?assertMatch(
|
||||||
|
{ok, {{_, 200, _}, _, []}},
|
||||||
|
emqx_bridge_v2_testlib:list_bridges_http_api_v1()
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
t_start_stop(Config) ->
|
||||||
|
ok = emqx_bridge_v2_testlib:t_start_stop(Config, mqtt_connector_stopped),
|
||||||
|
ok.
|
Loading…
Reference in New Issue