refactor: split greptimedb bridge to actions and connectors

This commit is contained in:
Serge Tupchii 2024-01-17 09:56:59 +02:00
parent c93fabb5cc
commit 2d693402c5
13 changed files with 368 additions and 120 deletions

View File

@ -101,7 +101,8 @@ hard_coded_action_info_modules_ee() ->
emqx_bridge_redis_action_info, emqx_bridge_redis_action_info,
emqx_bridge_iotdb_action_info, emqx_bridge_iotdb_action_info,
emqx_bridge_es_action_info, emqx_bridge_es_action_info,
emqx_bridge_opents_action_info emqx_bridge_opents_action_info,
emqx_bridge_greptimedb_action_info
]. ].
-else. -else.
hard_coded_action_info_modules_ee() -> hard_coded_action_info_modules_ee() ->

View File

@ -6,7 +6,7 @@
{emqx_connector, {path, "../../apps/emqx_connector"}}, {emqx_connector, {path, "../../apps/emqx_connector"}},
{emqx_resource, {path, "../../apps/emqx_resource"}}, {emqx_resource, {path, "../../apps/emqx_resource"}},
{emqx_bridge, {path, "../../apps/emqx_bridge"}}, {emqx_bridge, {path, "../../apps/emqx_bridge"}},
{greptimedb, {git, "https://github.com/GreptimeTeam/greptimedb-client-erl", {tag, "v0.1.6"}}} {greptimedb, {git, "https://github.com/GreptimeTeam/greptimedb-client-erl", {tag, "v0.1.7"}}}
]}. ]}.
{plugins, [rebar3_path_deps]}. {plugins, [rebar3_path_deps]}.
{project_plugins, [erlfmt]}. {project_plugins, [erlfmt]}.

View File

@ -8,7 +8,7 @@
emqx_resource, emqx_resource,
greptimedb greptimedb
]}, ]},
{env, []}, {env, [{emqx_action_info_modules, [emqx_bridge_greptimedb_action_info]}]},
{modules, []}, {modules, []},
{links, []} {links, []}
]}. ]}.

View File

@ -10,10 +10,6 @@
-import(hoconsc, [mk/2, enum/1, ref/2]). -import(hoconsc, [mk/2, enum/1, ref/2]).
-export([
conn_bridge_examples/1
]).
-export([ -export([
namespace/0, namespace/0,
roots/0, roots/0,
@ -21,6 +17,16 @@
desc/1 desc/1
]). ]).
%% Examples
-export([
bridge_v2_examples/1,
conn_bridge_examples/1,
connector_examples/1
]).
-define(CONNECTOR_TYPE, greptimedb).
-define(ACTION_TYPE, greptimedb).
%% ------------------------------------------------------------------------------------------------- %% -------------------------------------------------------------------------------------------------
%% api %% api
@ -29,44 +35,67 @@ conn_bridge_examples(Method) ->
#{ #{
<<"greptimedb">> => #{ <<"greptimedb">> => #{
summary => <<"Greptimedb HTTP API V2 Bridge">>, summary => <<"Greptimedb HTTP API V2 Bridge">>,
value => values("greptimedb", Method) value => bridge_v1_values(Method)
} }
} }
]. ].
values(Protocol, get) -> bridge_v2_examples(Method) ->
values(Protocol, post); ParamsExample = #{
values("greptimedb", post) -> parameters => #{
SupportUint = <<"uint_value=${payload.uint_key}u,">>, write_syntax => write_syntax_value(), precision => ms
TypeOpts = #{ }
bucket => <<"example_bucket">>,
org => <<"examlpe_org">>,
token => <<"example_token">>,
server => <<"127.0.0.1:4001">>
}, },
values(common, "greptimedb", SupportUint, TypeOpts); [
values(Protocol, put) -> #{
values(Protocol, post). <<"greptimedb">> => #{
summary => <<"GreptimeDB Action">>,
value => emqx_bridge_v2_schema:action_values(
Method, greptimedb, greptimedb, ParamsExample
)
}
}
].
values(common, Protocol, SupportUint, TypeOpts) -> connector_examples(Method) ->
CommonConfigs = #{ [
type => list_to_atom(Protocol), #{
<<"greptimedb">> => #{
summary => <<"GreptimeDB Connector">>,
value => emqx_connector_schema:connector_values(
Method, greptimedb, connector_values(Method)
)
}
}
].
bridge_v1_values(_Method) ->
#{
type => greptimedb,
name => <<"demo">>, name => <<"demo">>,
enable => true, enable => true,
local_topic => <<"local/topic/#">>, local_topic => <<"local/topic/#">>,
write_syntax => write_syntax => write_syntax_value(),
<<"${topic},clientid=${clientid}", " ", "payload=${payload},",
"${clientid}_int_value=${payload.int_key}i,", SupportUint/binary,
"bool=${payload.bool}">>,
precision => ms, precision => ms,
resource_opts => #{ resource_opts => #{
batch_size => 100, batch_size => 100,
batch_time => <<"20ms">> batch_time => <<"20ms">>
}, },
username => <<"example_username">>,
password => <<"******">>,
dbname => <<"example_db">>,
server => <<"127.0.0.1:4001">>, server => <<"127.0.0.1:4001">>,
ssl => #{enable => false} ssl => #{enable => false}
}, }.
maps:merge(TypeOpts, CommonConfigs).
connector_values(Method) ->
maps:without([write_syntax, precision], bridge_v1_values(Method)).
write_syntax_value() ->
<<"${topic},clientid=${clientid}", " ", "payload=${payload},",
"${clientid}_int_value=${payload.int_key}i,",
"uint_value=${payload.uint_key}u,"
"bool=${payload.bool}">>.
%% ------------------------------------------------------------------------------------------------- %% -------------------------------------------------------------------------------------------------
%% Hocon Schema Definitions %% Hocon Schema Definitions
@ -80,11 +109,50 @@ fields("put_grpc_v1") ->
method_fields(put, greptimedb); method_fields(put, greptimedb);
fields("get_grpc_v1") -> fields("get_grpc_v1") ->
method_fields(get, greptimedb); method_fields(get, greptimedb);
fields(Type) when fields(greptimedb = Type) ->
Type == greptimedb
->
greptimedb_bridge_common_fields() ++ greptimedb_bridge_common_fields() ++
connector_fields(Type). connector_fields(Type);
%% Actions
fields(action) ->
{greptimedb,
mk(
hoconsc:map(name, ref(?MODULE, greptimedb_action)),
#{desc => <<"GreptimeDB Action Config">>, required => false}
)};
fields(greptimedb_action) ->
emqx_bridge_v2_schema:make_producer_action_schema(
mk(ref(?MODULE, action_parameters), #{
required => true, desc => ?DESC(action_parameters)
})
);
fields(action_parameters) ->
[
{write_syntax, fun write_syntax/1},
emqx_bridge_greptimedb_connector:precision_field()
];
%% Connectors
fields("config_connector") ->
emqx_connector_schema:common_fields() ++
emqx_bridge_greptimedb_connector:fields("connector") ++
emqx_connector_schema:resource_opts_ref(?MODULE, connector_resource_opts);
fields(connector_resource_opts) ->
emqx_connector_schema:resource_opts_fields();
fields(Field) when
Field == "get_connector";
Field == "put_connector";
Field == "post_connector"
->
Fields =
emqx_bridge_greptimedb_connector:fields("connector") ++
emqx_connector_schema:resource_opts_ref(?MODULE, connector_resource_opts),
emqx_connector_schema:api_fields(Field, ?CONNECTOR_TYPE, Fields);
%$ Bridge v2
fields(Field) when
Field == "get_bridge_v2";
Field == "post_bridge_v2";
Field == "put_bridge_v2"
->
emqx_bridge_v2_schema:api_fields(Field, ?ACTION_TYPE, fields(greptimedb_action)).
method_fields(post, ConnectorType) -> method_fields(post, ConnectorType) ->
greptimedb_bridge_common_fields() ++ greptimedb_bridge_common_fields() ++
@ -122,6 +190,14 @@ desc(Method) when Method =:= "get"; Method =:= "put"; Method =:= "post" ->
["Configuration for Greptimedb using `", string:to_upper(Method), "` method."]; ["Configuration for Greptimedb using `", string:to_upper(Method), "` method."];
desc(greptimedb) -> desc(greptimedb) ->
?DESC(emqx_bridge_greptimedb_connector, "greptimedb"); ?DESC(emqx_bridge_greptimedb_connector, "greptimedb");
desc(greptimedb_action) ->
?DESC(greptimedb_action);
desc(action_parameters) ->
?DESC(action_parameters);
desc("config_connector") ->
?DESC("desc_config");
desc(connector_resource_opts) ->
?DESC(emqx_resource_schema, "resource_opts");
desc(_) -> desc(_) ->
undefined. undefined.

View File

@ -0,0 +1,58 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
%%--------------------------------------------------------------------
-module(emqx_bridge_greptimedb_action_info).
-behaviour(emqx_action_info).
-export([
action_type_name/0,
bridge_v1_config_to_action_config/2,
bridge_v1_config_to_connector_config/1,
bridge_v1_type_name/0,
connector_action_config_to_bridge_v1_config/2,
connector_type_name/0,
schema_module/0
]).
-import(emqx_utils_conv, [bin/1]).
-define(SCHEMA_MODULE, emqx_bridge_greptimedb).
-define(GREPTIMEDB_TYPE, greptimedb).
action_type_name() -> ?GREPTIMEDB_TYPE.
bridge_v1_type_name() -> ?GREPTIMEDB_TYPE.
connector_type_name() -> ?GREPTIMEDB_TYPE.
schema_module() -> ?SCHEMA_MODULE.
bridge_v1_config_to_action_config(BridgeV1Config, ConnectorName) ->
ActionTopLevelKeys = schema_keys(greptimedb_action),
ActionParametersKeys = schema_keys(action_parameters),
ActionKeys = ActionTopLevelKeys ++ ActionParametersKeys,
ActionConfig = make_config_map(ActionKeys, ActionParametersKeys, BridgeV1Config),
emqx_utils_maps:update_if_present(
<<"resource_opts">>,
fun emqx_bridge_v2_schema:project_to_actions_resource_opts/1,
ActionConfig#{<<"connector">> => ConnectorName}
).
bridge_v1_config_to_connector_config(BridgeV1Config) ->
ConnectorKeys = schema_keys("config_connector"),
emqx_utils_maps:update_if_present(
<<"resource_opts">>,
fun emqx_connector_schema:project_to_connector_resource_opts/1,
maps:with(ConnectorKeys, BridgeV1Config)
).
connector_action_config_to_bridge_v1_config(ConnectorRawConf, ActionRawConf) ->
emqx_action_info:connector_action_config_to_bridge_v1_config(
ConnectorRawConf, ActionRawConf
).
make_config_map(PickKeys, IndentKeys, Config) ->
Conf0 = maps:with(PickKeys, Config),
emqx_utils_maps:indent(<<"parameters">>, IndentKeys, Conf0).
schema_keys(Name) ->
[bin(Key) || Key <- proplists:get_keys(?SCHEMA_MODULE:fields(Name))].

View File

@ -4,7 +4,7 @@
-module(emqx_bridge_greptimedb_connector). -module(emqx_bridge_greptimedb_connector).
-include_lib("emqx_connector/include/emqx_connector.hrl"). -include_lib("emqx_connector/include/emqx_connector.hrl").
-include_lib("emqx_resource/include/emqx_resource.hrl").
-include_lib("hocon/include/hoconsc.hrl"). -include_lib("hocon/include/hoconsc.hrl").
-include_lib("typerefl/include/types.hrl"). -include_lib("typerefl/include/types.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
@ -19,6 +19,10 @@
callback_mode/0, callback_mode/0,
on_start/2, on_start/2,
on_stop/2, on_stop/2,
on_add_channel/4,
on_remove_channel/3,
on_get_channel_status/3,
on_get_channels/1,
on_query/3, on_query/3,
on_batch_query/3, on_batch_query/3,
on_query_async/4, on_query_async/4,
@ -34,6 +38,8 @@
desc/1 desc/1
]). ]).
-export([precision_field/0]).
%% only for test %% only for test
-ifdef(TEST). -ifdef(TEST).
-export([is_unrecoverable_error/1]). -export([is_unrecoverable_error/1]).
@ -62,6 +68,38 @@
%% resource callback %% resource callback
callback_mode() -> async_if_possible. callback_mode() -> async_if_possible.
on_add_channel(
_InstanceId,
#{channels := Channels} = OldState,
ChannelId,
#{parameters := Parameters} = ChannelConfig0
) ->
#{write_syntax := WriteSyntaxTmpl} = Parameters,
Precision = maps:get(precision, Parameters, ms),
ChannelConfig = maps:merge(
Parameters,
ChannelConfig0#{
precision => Precision,
write_syntax => to_config(WriteSyntaxTmpl, Precision)
}
),
{ok, OldState#{
channels => Channels#{ChannelId => ChannelConfig}
}}.
on_remove_channel(_InstanceId, #{channels := Channels} = State, ChannelId) ->
NewState = State#{channels => maps:remove(ChannelId, Channels)},
{ok, NewState}.
on_get_channel_status(InstanceId, _ChannelId, State) ->
case on_get_status(InstanceId, State) of
?status_connected -> ?status_connected;
_ -> ?status_connecting
end.
on_get_channels(InstanceId) ->
emqx_bridge_v2:get_channels_for_connector(InstanceId).
on_start(InstId, Config) -> on_start(InstId, Config) ->
%% InstID as pool would be handled by greptimedb client %% InstID as pool would be handled by greptimedb client
%% so there is no need to allocate pool_name here %% so there is no need to allocate pool_name here
@ -78,8 +116,13 @@ on_stop(InstId, _State) ->
ok ok
end. end.
on_query(InstId, {send_message, Data}, _State = #{write_syntax := SyntaxLines, client := Client}) -> on_query(InstId, {Channel, Message}, State) ->
case data_to_points(Data, SyntaxLines) of #{
channels := #{Channel := #{write_syntax := SyntaxLines}},
client := Client,
dbname := DbName
} = State,
case data_to_points(Message, DbName, SyntaxLines) of
{ok, Points} -> {ok, Points} ->
?tp( ?tp(
greptimedb_connector_send_query, greptimedb_connector_send_query,
@ -97,8 +140,13 @@ on_query(InstId, {send_message, Data}, _State = #{write_syntax := SyntaxLines, c
%% Once a Batched Data trans to points failed. %% Once a Batched Data trans to points failed.
%% This batch query failed %% This batch query failed
on_batch_query(InstId, BatchData, _State = #{write_syntax := SyntaxLines, client := Client}) -> on_batch_query(InstId, [{Channel, _} | _] = BatchData, State) ->
case parse_batch_data(InstId, BatchData, SyntaxLines) of #{
channels := #{Channel := #{write_syntax := SyntaxLines}},
client := Client,
dbname := DbName
} = State,
case parse_batch_data(InstId, DbName, BatchData, SyntaxLines) of
{ok, Points} -> {ok, Points} ->
?tp( ?tp(
greptimedb_connector_send_query, greptimedb_connector_send_query,
@ -113,13 +161,13 @@ on_batch_query(InstId, BatchData, _State = #{write_syntax := SyntaxLines, client
{error, {unrecoverable_error, Reason}} {error, {unrecoverable_error, Reason}}
end. end.
on_query_async( on_query_async(InstId, {Channel, Message}, {ReplyFun, Args}, State) ->
InstId, #{
{send_message, Data}, channels := #{Channel := #{write_syntax := SyntaxLines}},
{ReplyFun, Args}, client := Client,
_State = #{write_syntax := SyntaxLines, client := Client} dbname := DbName
) -> } = State,
case data_to_points(Data, SyntaxLines) of case data_to_points(Message, DbName, SyntaxLines) of
{ok, Points} -> {ok, Points} ->
?tp( ?tp(
greptimedb_connector_send_query, greptimedb_connector_send_query,
@ -135,13 +183,13 @@ on_query_async(
Err Err
end. end.
on_batch_query_async( on_batch_query_async(InstId, [{Channel, _} | _] = BatchData, {ReplyFun, Args}, State) ->
InstId, #{
BatchData, channels := #{Channel := #{write_syntax := SyntaxLines}},
{ReplyFun, Args}, client := Client,
#{write_syntax := SyntaxLines, client := Client} dbname := DbName
) -> } = State,
case parse_batch_data(InstId, BatchData, SyntaxLines) of case parse_batch_data(InstId, DbName, BatchData, SyntaxLines) of
{ok, Points} -> {ok, Points} ->
?tp( ?tp(
greptimedb_connector_send_query, greptimedb_connector_send_query,
@ -159,9 +207,9 @@ on_batch_query_async(
on_get_status(_InstId, #{client := Client}) -> on_get_status(_InstId, #{client := Client}) ->
case greptimedb:is_alive(Client) of case greptimedb:is_alive(Client) of
true -> true ->
connected; ?status_connected;
false -> false ->
disconnected ?status_disconnected
end. end.
%% ------------------------------------------------------------------------------------------------- %% -------------------------------------------------------------------------------------------------
@ -179,22 +227,36 @@ roots() ->
}} }}
]. ].
fields("connector") ->
[server_field()] ++
credentials_fields() ++
emqx_connector_schema_lib:ssl_fields();
%% ============ begin: schema for old bridge configs ============
fields(common) -> fields(common) ->
[ [
{server, server()}, server_field(),
{precision, precision_field()
%% The greptimedb only supports these 4 precision
mk(enum([ns, us, ms, s]), #{
required => false, default => ms, desc => ?DESC("precision")
})}
]; ];
fields(greptimedb) -> fields(greptimedb) ->
fields(common) ++ fields(common) ++
[ credentials_fields() ++
{dbname, mk(binary(), #{required => true, desc => ?DESC("dbname")})}, emqx_connector_schema_lib:ssl_fields().
{username, mk(binary(), #{desc => ?DESC("username")})}, %% ============ end: schema for old bridge configs ============
{password, emqx_schema_secret:mk(#{desc => ?DESC("password")})}
] ++ emqx_connector_schema_lib:ssl_fields(). desc(common) ->
?DESC("common");
desc(greptimedb) ->
?DESC("greptimedb").
precision_field() ->
{precision,
%% The greptimedb only supports these 4 precision
mk(enum([ns, us, ms, s]), #{
required => false, default => ms, desc => ?DESC("precision")
})}.
server_field() ->
{server, server()}.
server() -> server() ->
Meta = #{ Meta = #{
@ -205,10 +267,12 @@ server() ->
}, },
emqx_schema:servers_sc(Meta, ?GREPTIMEDB_HOST_OPTIONS). emqx_schema:servers_sc(Meta, ?GREPTIMEDB_HOST_OPTIONS).
desc(common) -> credentials_fields() ->
?DESC("common"); [
desc(greptimedb) -> {dbname, mk(binary(), #{required => true, desc => ?DESC("dbname")})},
?DESC("greptimedb"). {username, mk(binary(), #{desc => ?DESC("username")})},
{password, emqx_schema_secret:mk(#{desc => ?DESC("password")})}
].
%% ------------------------------------------------------------------------------------------------- %% -------------------------------------------------------------------------------------------------
%% internal functions %% internal functions
@ -243,9 +307,8 @@ start_client(InstId, Config) ->
do_start_client( do_start_client(
InstId, InstId,
ClientConfig, ClientConfig,
Config = #{write_syntax := Lines} Config
) -> ) ->
Precision = maps:get(precision, Config, ms),
case greptimedb:start_client(ClientConfig) of case greptimedb:start_client(ClientConfig) of
{ok, Client} -> {ok, Client} ->
case greptimedb:is_alive(Client, true) of case greptimedb:is_alive(Client, true) of
@ -253,7 +316,7 @@ do_start_client(
State = #{ State = #{
client => Client, client => Client,
dbname => proplists:get_value(dbname, ClientConfig, ?DEFAULT_DB), dbname => proplists:get_value(dbname, ClientConfig, ?DEFAULT_DB),
write_syntax => to_config(Lines, Precision) channels => #{}
}, },
?SLOG(info, #{ ?SLOG(info, #{
msg => "starting_greptimedb_connector_success", msg => "starting_greptimedb_connector_success",
@ -314,8 +377,7 @@ client_config(
{pool, InstId}, {pool, InstId},
{pool_type, random}, {pool_type, random},
{auto_reconnect, ?AUTO_RECONNECT_S}, {auto_reconnect, ?AUTO_RECONNECT_S},
{gprc_options, grpc_config()}, {gprc_options, grpc_config()}
{timeunit, maps:get(precision, Config, ms)}
] ++ protocol_config(Config). ] ++ protocol_config(Config).
protocol_config( protocol_config(
@ -469,10 +531,10 @@ to_maps_config(K, V, Res) ->
%% ------------------------------------------------------------------------------------------------- %% -------------------------------------------------------------------------------------------------
%% Tags & Fields Data Trans %% Tags & Fields Data Trans
parse_batch_data(InstId, BatchData, SyntaxLines) -> parse_batch_data(InstId, DbName, BatchData, SyntaxLines) ->
{Points, Errors} = lists:foldl( {Points, Errors} = lists:foldl(
fun({send_message, Data}, {ListOfPoints, ErrAccIn}) -> fun({_, Data}, {ListOfPoints, ErrAccIn}) ->
case data_to_points(Data, SyntaxLines) of case data_to_points(Data, DbName, SyntaxLines) of
{ok, Points} -> {ok, Points} ->
{[Points | ListOfPoints], ErrAccIn}; {[Points | ListOfPoints], ErrAccIn};
{error, ErrorPoints} -> {error, ErrorPoints} ->
@ -496,21 +558,25 @@ parse_batch_data(InstId, BatchData, SyntaxLines) ->
{error, points_trans_failed} {error, points_trans_failed}
end. end.
-spec data_to_points(map(), [ -spec data_to_points(
#{ map(),
fields := [{binary(), binary()}], binary(),
measurement := binary(), [
tags := [{binary(), binary()}], #{
timestamp := emqx_placeholder:tmpl_token() | integer(), fields := [{binary(), binary()}],
precision := {From :: ts_precision(), To :: ts_precision()} measurement := binary(),
} tags := [{binary(), binary()}],
]) -> {ok, [map()]} | {error, term()}. timestamp := emqx_placeholder:tmpl_token() | integer(),
data_to_points(Data, SyntaxLines) -> precision := {From :: ts_precision(), To :: ts_precision()}
lines_to_points(Data, SyntaxLines, [], []). }
]
) -> {ok, [map()]} | {error, term()}.
data_to_points(Data, DbName, SyntaxLines) ->
lines_to_points(Data, DbName, SyntaxLines, [], []).
%% When converting multiple rows data into Greptimedb Line Protocol, they are considered to be strongly correlated. %% When converting multiple rows data into Greptimedb Line Protocol, they are considered to be strongly correlated.
%% And once a row fails to convert, all of them are considered to have failed. %% And once a row fails to convert, all of them are considered to have failed.
lines_to_points(_, [], Points, ErrorPoints) -> lines_to_points(_Data, _DbName, [], Points, ErrorPoints) ->
case ErrorPoints of case ErrorPoints of
[] -> [] ->
{ok, Points}; {ok, Points};
@ -518,23 +584,27 @@ lines_to_points(_, [], Points, ErrorPoints) ->
%% ignore trans succeeded points %% ignore trans succeeded points
{error, ErrorPoints} {error, ErrorPoints}
end; end;
lines_to_points(Data, [#{timestamp := Ts} = Item | Rest], ResultPointsAcc, ErrorPointsAcc) when lines_to_points(
Data, DbName, [#{timestamp := Ts} = Item | Rest], ResultPointsAcc, ErrorPointsAcc
) when
is_list(Ts) is_list(Ts)
-> ->
TransOptions = #{return => rawlist, var_trans => fun data_filter/1}, TransOptions = #{return => rawlist, var_trans => fun data_filter/1},
case parse_timestamp(emqx_placeholder:proc_tmpl(Ts, Data, TransOptions)) of case parse_timestamp(emqx_placeholder:proc_tmpl(Ts, Data, TransOptions)) of
{ok, TsInt} -> {ok, TsInt} ->
Item1 = Item#{timestamp => TsInt}, Item1 = Item#{timestamp => TsInt},
continue_lines_to_points(Data, Item1, Rest, ResultPointsAcc, ErrorPointsAcc); continue_lines_to_points(Data, DbName, Item1, Rest, ResultPointsAcc, ErrorPointsAcc);
{error, BadTs} -> {error, BadTs} ->
lines_to_points(Data, Rest, ResultPointsAcc, [ lines_to_points(Data, DbName, Rest, ResultPointsAcc, [
{error, {bad_timestamp, BadTs}} | ErrorPointsAcc {error, {bad_timestamp, BadTs}} | ErrorPointsAcc
]) ])
end; end;
lines_to_points(Data, [#{timestamp := Ts} = Item | Rest], ResultPointsAcc, ErrorPointsAcc) when lines_to_points(
Data, DbName, [#{timestamp := Ts} = Item | Rest], ResultPointsAcc, ErrorPointsAcc
) when
is_integer(Ts) is_integer(Ts)
-> ->
continue_lines_to_points(Data, Item, Rest, ResultPointsAcc, ErrorPointsAcc). continue_lines_to_points(Data, DbName, Item, Rest, ResultPointsAcc, ErrorPointsAcc).
parse_timestamp([TsInt]) when is_integer(TsInt) -> parse_timestamp([TsInt]) when is_integer(TsInt) ->
{ok, TsInt}; {ok, TsInt};
@ -546,30 +616,32 @@ parse_timestamp([TsBin]) ->
{error, TsBin} {error, TsBin}
end. end.
continue_lines_to_points(Data, Item, Rest, ResultPointsAcc, ErrorPointsAcc) -> continue_lines_to_points(Data, DbName, Item, Rest, ResultPointsAcc, ErrorPointsAcc) ->
case line_to_point(Data, Item) of case line_to_point(Data, DbName, Item) of
{_, [#{fields := Fields}]} when map_size(Fields) =:= 0 -> {_, [#{fields := Fields}]} when map_size(Fields) =:= 0 ->
%% greptimedb client doesn't like empty field maps... %% greptimedb client doesn't like empty field maps...
ErrorPointsAcc1 = [{error, no_fields} | ErrorPointsAcc], ErrorPointsAcc1 = [{error, no_fields} | ErrorPointsAcc],
lines_to_points(Data, Rest, ResultPointsAcc, ErrorPointsAcc1); lines_to_points(Data, DbName, Rest, ResultPointsAcc, ErrorPointsAcc1);
Point -> Point ->
lines_to_points(Data, Rest, [Point | ResultPointsAcc], ErrorPointsAcc) lines_to_points(Data, DbName, Rest, [Point | ResultPointsAcc], ErrorPointsAcc)
end. end.
line_to_point( line_to_point(
Data, Data,
DbName,
#{ #{
measurement := Measurement, measurement := Measurement,
tags := Tags, tags := Tags,
fields := Fields, fields := Fields,
timestamp := Ts, timestamp := Ts,
precision := Precision precision := {_, ToPrecision} = Precision
} = Item } = Item
) -> ) ->
{_, EncodedTags} = maps:fold(fun maps_config_to_data/3, {Data, #{}}, Tags), {_, EncodedTags} = maps:fold(fun maps_config_to_data/3, {Data, #{}}, Tags),
{_, EncodedFields} = maps:fold(fun maps_config_to_data/3, {Data, #{}}, Fields), {_, EncodedFields} = maps:fold(fun maps_config_to_data/3, {Data, #{}}, Fields),
TableName = emqx_placeholder:proc_tmpl(Measurement, Data), TableName = emqx_placeholder:proc_tmpl(Measurement, Data),
{TableName, [ Metric = #{dbname => DbName, table => TableName, timeunit => ToPrecision},
{Metric, [
maps:without([precision, measurement], Item#{ maps:without([precision, measurement], Item#{
tags => EncodedTags, tags => EncodedTags,
fields => EncodedFields, fields => EncodedFields,

View File

@ -452,10 +452,7 @@ t_start_ok(Config) ->
[#{points := [Point0]}] = Trace, [#{points := [Point0]}] = Trace,
{Measurement, [Point]} = Point0, {Measurement, [Point]} = Point0,
ct:pal("sent point: ~p", [Point]), ct:pal("sent point: ~p", [Point]),
?assertMatch( ?assertMatch(#{dbname := _, table := _, timeunit := _}, Measurement),
<<_/binary>>,
Measurement
),
?assertMatch( ?assertMatch(
#{ #{
fields := #{}, fields := #{},
@ -481,7 +478,6 @@ t_start_stop(Config) ->
BridgeName = ?config(bridge_name, Config), BridgeName = ?config(bridge_name, Config),
BridgeConfig = ?config(bridge_config, Config), BridgeConfig = ?config(bridge_config, Config),
StopTracePoint = greptimedb_client_stopped, StopTracePoint = greptimedb_client_stopped,
ResourceId = emqx_bridge_resource:resource_id(BridgeType, BridgeName),
?check_trace( ?check_trace(
begin begin
ProbeRes0 = emqx_bridge_testlib:probe_bridge_api( ProbeRes0 = emqx_bridge_testlib:probe_bridge_api(
@ -491,6 +487,7 @@ t_start_stop(Config) ->
), ),
?assertMatch({ok, {{_, 204, _}, _Headers, _Body}}, ProbeRes0), ?assertMatch({ok, {{_, 204, _}, _Headers, _Body}}, ProbeRes0),
?assertMatch({ok, _}, emqx_bridge:create(BridgeType, BridgeName, BridgeConfig)), ?assertMatch({ok, _}, emqx_bridge:create(BridgeType, BridgeName, BridgeConfig)),
ResourceId = emqx_bridge_resource:resource_id(BridgeType, BridgeName),
%% 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.
@ -554,6 +551,7 @@ t_start_stop(Config) ->
ok ok
end, end,
fun(Trace) -> fun(Trace) ->
ResourceId = emqx_bridge_resource:resource_id(BridgeType, BridgeName),
%% one for probe, two for real %% one for probe, two for real
?assertMatch( ?assertMatch(
[_, #{instance_id := ResourceId}, #{instance_id := ResourceId}], [_, #{instance_id := ResourceId}, #{instance_id := ResourceId}],
@ -568,10 +566,7 @@ t_start_already_started(Config) ->
Type = greptimedb_type_bin(?config(greptimedb_type, Config)), Type = greptimedb_type_bin(?config(greptimedb_type, Config)),
Name = ?config(greptimedb_name, Config), Name = ?config(greptimedb_name, Config),
GreptimedbConfigString = ?config(greptimedb_config_string, Config), GreptimedbConfigString = ?config(greptimedb_config_string, Config),
?assertMatch( ?assertMatch({ok, _}, create_bridge(Config)),
{ok, _},
create_bridge(Config)
),
ResourceId = resource_id(Config), ResourceId = resource_id(Config),
TypeAtom = binary_to_atom(Type), TypeAtom = binary_to_atom(Type),
NameAtom = binary_to_atom(Name), NameAtom = binary_to_atom(Name),
@ -1036,7 +1031,6 @@ t_missing_field(Config) ->
ok. ok.
t_authentication_error_on_send_message(Config0) -> t_authentication_error_on_send_message(Config0) ->
ResourceId = resource_id(Config0),
QueryMode = proplists:get_value(query_mode, Config0, sync), QueryMode = proplists:get_value(query_mode, Config0, sync),
GreptimedbType = ?config(greptimedb_type, Config0), GreptimedbType = ?config(greptimedb_type, Config0),
GreptimeConfig0 = proplists:get_value(greptimedb_config, Config0), GreptimeConfig0 = proplists:get_value(greptimedb_config, Config0),
@ -1055,6 +1049,7 @@ t_authentication_error_on_send_message(Config0) ->
end, end,
fun() -> fun() ->
{ok, _} = create_bridge(Config), {ok, _} = create_bridge(Config),
ResourceId = resource_id(Config0),
?retry( ?retry(
_Sleep = 1_000, _Sleep = 1_000,
_Attempts = 10, _Attempts = 10,

View File

@ -65,7 +65,7 @@ t_lifecycle(Config) ->
Port = ?config(greptimedb_tcp_port, Config), Port = ?config(greptimedb_tcp_port, Config),
perform_lifecycle_check( perform_lifecycle_check(
<<"emqx_bridge_greptimedb_connector_SUITE">>, <<"emqx_bridge_greptimedb_connector_SUITE">>,
greptimedb_config(Host, Port) greptimedb_connector_config(Host, Port)
). ).
perform_lifecycle_check(PoolName, InitialConfig) -> perform_lifecycle_check(PoolName, InitialConfig) ->
@ -75,6 +75,7 @@ perform_lifecycle_check(PoolName, InitialConfig) ->
% expects this % expects this
FullConfig = CheckedConfig#{write_syntax => greptimedb_write_syntax()}, FullConfig = CheckedConfig#{write_syntax => greptimedb_write_syntax()},
{ok, #{ {ok, #{
id := ResourceId,
state := #{client := #{pool := ReturnedPoolName}} = State, state := #{client := #{pool := ReturnedPoolName}} = State,
status := InitialStatus status := InitialStatus
}} = emqx_resource:create_local( }} = emqx_resource:create_local(
@ -92,8 +93,13 @@ perform_lifecycle_check(PoolName, InitialConfig) ->
}} = }} =
emqx_resource:get_instance(PoolName), emqx_resource:get_instance(PoolName),
?assertEqual({ok, connected}, emqx_resource:health_check(PoolName)), ?assertEqual({ok, connected}, emqx_resource:health_check(PoolName)),
%% install actions to the connector
ActionConfig = greptimedb_action_config(),
ChannelId = <<"test_channel">>,
?assertEqual(ok, emqx_resource_manager:add_channel(ResourceId, ChannelId, ActionConfig)),
?assertMatch(#{status := connected}, emqx_resource:channel_health_check(ResourceId, ChannelId)),
% % Perform query as further check that the resource is working as expected % % Perform query as further check that the resource is working as expected
?assertMatch({ok, _}, emqx_resource:query(PoolName, test_query())), ?assertMatch({ok, _}, emqx_resource:query(PoolName, test_query(ChannelId))),
?assertEqual(ok, emqx_resource:stop(PoolName)), ?assertEqual(ok, emqx_resource:stop(PoolName)),
% Resource will be listed still, but state will be changed and healthcheck will fail % Resource will be listed still, but state will be changed and healthcheck will fail
% as the worker no longer exists. % as the worker no longer exists.
@ -115,7 +121,9 @@ perform_lifecycle_check(PoolName, InitialConfig) ->
{ok, ?CONNECTOR_RESOURCE_GROUP, #{status := InitialStatus}} = {ok, ?CONNECTOR_RESOURCE_GROUP, #{status := InitialStatus}} =
emqx_resource:get_instance(PoolName), emqx_resource:get_instance(PoolName),
?assertEqual({ok, connected}, emqx_resource:health_check(PoolName)), ?assertEqual({ok, connected}, emqx_resource:health_check(PoolName)),
?assertMatch({ok, _}, emqx_resource:query(PoolName, test_query())), ?assertEqual(ok, emqx_resource_manager:add_channel(ResourceId, ChannelId, ActionConfig)),
?assertMatch(#{status := connected}, emqx_resource:channel_health_check(ResourceId, ChannelId)),
?assertMatch({ok, _}, emqx_resource:query(PoolName, test_query(ChannelId))),
% Stop and remove the resource in one go. % Stop and remove the resource in one go.
?assertEqual(ok, emqx_resource:remove_local(PoolName)), ?assertEqual(ok, emqx_resource:remove_local(PoolName)),
?assertEqual({error, not_found}, ecpool:stop_sup_pool(ReturnedPoolName)), ?assertEqual({error, not_found}, ecpool:stop_sup_pool(ReturnedPoolName)),
@ -126,7 +134,7 @@ perform_lifecycle_check(PoolName, InitialConfig) ->
% %% Helpers % %% Helpers
% %%------------------------------------------------------------------------------ % %%------------------------------------------------------------------------------
greptimedb_config(Host, Port) -> greptimedb_connector_config(Host, Port) ->
Server = list_to_binary(io_lib:format("~s:~b", [Host, Port])), Server = list_to_binary(io_lib:format("~s:~b", [Host, Port])),
ResourceConfig = #{ ResourceConfig = #{
<<"dbname">> => <<"public">>, <<"dbname">> => <<"public">>,
@ -136,6 +144,14 @@ greptimedb_config(Host, Port) ->
}, },
#{<<"config">> => ResourceConfig}. #{<<"config">> => ResourceConfig}.
greptimedb_action_config() ->
#{
parameters => #{
write_syntax => greptimedb_write_syntax(),
precision => ms
}
}.
greptimedb_write_syntax() -> greptimedb_write_syntax() ->
[ [
#{ #{
@ -146,8 +162,8 @@ greptimedb_write_syntax() ->
} }
]. ].
test_query() -> test_query(ChannelId) ->
{send_message, #{ {ChannelId, #{
<<"clientid">> => <<"something">>, <<"clientid">> => <<"something">>,
<<"payload">> => #{bool => true}, <<"payload">> => #{bool => true},
<<"topic">> => <<"connector_test">>, <<"topic">> => <<"connector_test">>,

View File

@ -58,6 +58,8 @@ resource_type(elasticsearch) ->
emqx_bridge_es_connector; emqx_bridge_es_connector;
resource_type(opents) -> resource_type(opents) ->
emqx_bridge_opents_connector; emqx_bridge_opents_connector;
resource_type(greptimedb) ->
emqx_bridge_greptimedb_connector;
resource_type(Type) -> resource_type(Type) ->
error({unknown_connector_type, Type}). error({unknown_connector_type, Type}).
@ -225,6 +227,14 @@ connector_structs() ->
desc => <<"OpenTSDB Connector Config">>, desc => <<"OpenTSDB Connector Config">>,
required => false required => false
} }
)},
{greptimedb,
mk(
hoconsc:map(name, ref(emqx_bridge_greptimedb, "config_connector")),
#{
desc => <<"GreptimeDB Connector Config">>,
required => false
}
)} )}
]. ].
@ -247,7 +257,8 @@ schema_modules() ->
emqx_bridge_redis_schema, emqx_bridge_redis_schema,
emqx_bridge_iotdb_connector, emqx_bridge_iotdb_connector,
emqx_bridge_es_connector, emqx_bridge_es_connector,
emqx_bridge_opents_connector emqx_bridge_opents_connector,
emqx_bridge_greptimedb
]. ].
api_schemas(Method) -> api_schemas(Method) ->
@ -279,7 +290,8 @@ api_schemas(Method) ->
api_ref(emqx_bridge_redis_schema, <<"redis">>, Method ++ "_connector"), api_ref(emqx_bridge_redis_schema, <<"redis">>, Method ++ "_connector"),
api_ref(emqx_bridge_iotdb_connector, <<"iotdb">>, Method), api_ref(emqx_bridge_iotdb_connector, <<"iotdb">>, Method),
api_ref(emqx_bridge_es_connector, <<"elasticsearch">>, Method), api_ref(emqx_bridge_es_connector, <<"elasticsearch">>, Method),
api_ref(emqx_bridge_opents_connector, <<"opents">>, Method) api_ref(emqx_bridge_opents_connector, <<"opents">>, Method),
api_ref(emqx_bridge_greptimedb, <<"greptimedb">>, Method ++ "_connector")
]. ].
api_ref(Module, Type, Method) -> api_ref(Module, Type, Method) ->

View File

@ -160,7 +160,9 @@ connector_type_to_bridge_types(iotdb) ->
connector_type_to_bridge_types(elasticsearch) -> connector_type_to_bridge_types(elasticsearch) ->
[elasticsearch]; [elasticsearch];
connector_type_to_bridge_types(opents) -> connector_type_to_bridge_types(opents) ->
[opents]. [opents];
connector_type_to_bridge_types(greptimedb) ->
[greptimedb].
actions_config_name(action) -> <<"actions">>; actions_config_name(action) -> <<"actions">>;
actions_config_name(source) -> <<"sources">>. actions_config_name(source) -> <<"sources">>.

View File

@ -0,0 +1 @@
Split GreptimeDB bridge into connector and action components.

View File

@ -209,7 +209,7 @@ defmodule EMQXUmbrella.MixProject do
{:crc32cer, "0.1.8", override: true}, {:crc32cer, "0.1.8", override: true},
{:supervisor3, "1.1.12", override: true}, {:supervisor3, "1.1.12", override: true},
{:opentsdb, github: "emqx/opentsdb-client-erl", tag: "v0.5.1", override: true}, {:opentsdb, github: "emqx/opentsdb-client-erl", tag: "v0.5.1", override: true},
{:greptimedb, github: "GreptimeTeam/greptimedb-client-erl", tag: "v0.1.6", override: true}, {:greptimedb, github: "GreptimeTeam/greptimedb-client-erl", tag: "v0.1.7", override: true},
# The following two are dependencies of rabbit_common. They are needed here to # The following two are dependencies of rabbit_common. They are needed here to
# make mix not complain about conflicting versions # make mix not complain about conflicting versions
{:thoas, github: "emqx/thoas", tag: "v1.0.0", override: true}, {:thoas, github: "emqx/thoas", tag: "v1.0.0", override: true},

View File

@ -47,4 +47,19 @@ Please note that a placeholder for an integer value must be annotated with a suf
write_syntax.label: write_syntax.label:
"""Write Syntax""" """Write Syntax"""
action_parameters.label:
"""Action Parameters"""
action_parameters.desc:
"""Additional parameters specific to this action type"""
connector.label:
"""GreptimeDB Connector"""
connector.desc:
"""GreptimeDB Connector Configs"""
greptimedb_action.label:
"""GreptimeDB Action"""
greptimedb_action.desc:
"""Action to interact with a GreptimeDB connector"""
} }