Browse Source

caijinpeng

初始化
caijinpeng 4 years ago
commit
26e4f21741

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+*.sublime-project
+*.sublime-workspace
+*.lock
+_build/

BIN
.rebar3/rebar_compiler_erl/source.dag


+ 201 - 0
LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   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.

+ 28 - 0
Makefile

@@ -0,0 +1,28 @@
+## shallow clone for speed
+
+REBAR_GIT_CLONE_OPTIONS += --depth 1
+export REBAR_GIT_CLONE_OPTIONS
+
+REBAR = rebar3
+all: compile
+
+compile:
+	$(REBAR) compile
+
+ct: compile
+	$(REBAR) as test ct -v
+
+eunit: compile
+	$(REBAR) as test eunit
+
+xref:
+	$(REBAR) xref
+
+cover:
+	$(REBAR) cover
+
+clean: distclean
+
+distclean:
+	@rm -rf _build
+	@rm -f data/app.*.config data/vm.*.args rebar.lock

+ 88 - 0
README.md

@@ -0,0 +1,88 @@
+
+emqx-plugin-template
+====================
+
+This is a template plugin for the EMQ X broker. And you can see [Plugin Development Guide](https://developer.emqx.io/docs/emq/v3/en/plugins.html#plugin-development-guide) to learning how to use it.
+
+Plugin Config
+-------------
+
+Each plugin should have a 'etc/{plugin_name}.conf|config' file to store application config.
+
+Authentication and ACL
+----------------------
+
+```
+emqx:hook('client.authenticate', fun ?MODULE:on_client_authenticate/3, [Env]).
+emqx:hook('client.check_acl', fun ?MODULE:on_client_check_acl/5, [Env]).
+```
+
+Build the EMQX broker
+-----------------
+###### (1).基于CentOS7.5环境下编译,先安装相关插件
+```
+  yum -y install make gcc gcc-c++ glibc-devel glibc-headers kernel-devel kernel-headers m4 ncurses ncurses-devel openssl openssl-devel openssl-libs zlib zlib-devel libselinux-devel xmlto perl git wget
+  注意:openssl的版本不是1.1.1k,则需要通过源码openssl-1.1.1k.tar.gz来安装openssl
+```
+###### (2).准备Erlang/OTP 22环境 (推荐使用erlang22.3版本)
+```
+  esl-erlang_22.3.4.2-1_centos_7_amd64.rpm
+  (或 otp_src_22.3.tar.gz ) 
+  
+  注意:如果通过源码otp_src_22.3.tar.gz安装方式,如果中间有报错,如下错误:
+    jinterface     : No Java compiler found
+    odbc           : ODBC library - link check failed  
+
+    wx             : No OpenGL headers found, wx will NOT be usable
+    
+  错误的解决方法,一个一个来安装相关依赖包,如下:
+   (a).安装:yum install java-devel
+   (b).安装:yum install unixODBC-devel
+   (c).通过:yum list mesa-libGLU-devel*  然后安装 yum install mesa-libGLU-devel-9.0.1-1.ky10.aarch64
+```
+###### (3).下载EMQX源码
+通过git下载EMQX源码,执行此命令
+```
+  git clone -b v4.1.0 https://github.com/emqx/emqx-rel.git emqx-rel4.1
+```
+###### (4).修改EMQX文件,增加kafka插件
+下载成功后完成后进入emqx-rel4.1目录,编辑emqx-rel4.1目录下的rebar.config文件,如下:
+````
+(a). 在 {deps,
+ [emqx,
+  emqx_retainer,
+  emqx_management,
+  emqx_dashboard,
+   ...]}
+ 增加
+  {emqx_plugin_kafka, {git, "https://github.com/jameycai/emqx_plugin_kafka.git", {tag, "master"}}}
+ 修改后变成
+ {deps,
+ [emqx,
+  emqx_retainer,
+  emqx_management,
+  emqx_dashboard,
+   ..., 
+ {emqx_plugin_kafka, {git, "https://github.com/jameycai/emqx_plugin_kafka.git", {tag, "master"}}}
+]}
+
+(b). 增加 {emqx_plugin_kafka, load}
+````
+
+###### (5).编译EMQX,并且启动EMQX
+进入emqx-rel4.1目录,执行make命令,此过程会因为网络问题,多次出现错误导致停止,只需要不断地make直到成功。
+````
+  编译成功后,会出现_build目录,然后进入_build/emqx/rel/emqx/bin目录,启动emqx,如下:
+  ./emqx start  
+````
+
+ 
+License
+-------
+
+Apache License Version 2.0
+
+Author
+------
+
+EMQ X Team.

+ 3 - 0
TODO

@@ -0,0 +1,3 @@
+1. Add a script to generate plugin project
+2. Upgrade the README.md
+3. Add the plugin development guide

BIN
ebin/emqx_cli_demo_kafka.beam


+ 13 - 0
ebin/emqx_plugin_kafka.app

@@ -0,0 +1,13 @@
+{application,emqx_plugin_kafka,
+             [{description,"EMQ X plugin kafka bridge, Edit by yzs-lyg"},
+              {vsn,"master"},
+              {modules,[emqx_cli_demo_kafka,emqx_plugin_kafka,
+                        emqx_plugin_kafka_app,emqx_plugin_kafka_sup]},
+              {registered,[emqx_plugin_kafka_sup]},
+              {applications,[kernel,stdlib,ekaf]},
+              {mod,{emqx_plugin_kafka_app,[]}},
+              {env,[]},
+              {licenses,["Apache-2.0"]},
+              {maintainers,["lyg <3613840847@qq.com>"]},
+              {links,[{"Github",
+                       "https://github.com/yzs/emqx-plugin-kafka"}]}]}.

BIN
ebin/emqx_plugin_kafka.beam


BIN
ebin/emqx_plugin_kafka_app.beam


BIN
ebin/emqx_plugin_kafka_sup.beam


+ 31 - 0
etc/emqx_plugin_kafka.conf

@@ -0,0 +1,31 @@
+##--------------------------------------------------------------------
+## kafka Bridge
+##--------------------------------------------------------------------
+
+## The Kafka loadbalancer node host that bridge is listening on.
+##
+## Value: 127.0.0.1, localhost
+###kafka.host = 192.168.67.16
+kafka.host = 116.239.32.52
+
+## The kafka loadbalancer node port that bridge is listening on.
+##
+## Value: Port
+###kafka.port = 9092
+kafka.port = 8884
+
+## The kafka loadbalancer node partition strategy.
+##
+## Value: random, sticky_round_robin, strict_round_robin, custom
+kafka.partitionstrategy = strict_round_robin
+
+## Each worker represents a connection to a broker + topic + partition combination.
+## You can decide how many workers to start for each partition.
+##
+## Value: 
+kafka.partitionworkers = 8
+
+## payload topic.
+##
+## Value: string
+kafka.payloadtopic = Processing

+ 35 - 0
priv/emqx_plugin_kafka.schema

@@ -0,0 +1,35 @@
+%%-*- mode: erlang -*-
+
+{mapping, "kafka.host", "emqx_plugin_kafka.broker", [
+  {default, "192.168.0.128"},
+  {datatype, string}
+]}.
+
+{mapping, "kafka.port", "emqx_plugin_kafka.broker", [
+  {default, "9092"},
+  {datatype, string}
+]}.
+
+{mapping, "kafka.partitionstrategy", "emqx_plugin_kafka.broker", [
+  {default, "strict_round_robin"},
+  {datatype, string}
+]}.
+
+{mapping, "kafka.partitionworkers", "emqx_plugin_kafka.broker", [
+  {default, 8},
+  {datatype, integer}
+]}.
+
+{mapping, "kafka.payloadtopic", "emqx_plugin_kafka.broker", [
+  {default, "Processing"},
+  {datatype, string}
+]}.
+
+{translation, "emqx_plugin_kafka.broker", fun(Conf) ->
+  KafkaHost = cuttlefish:conf_get("kafka.host", Conf),
+  KafkaPort = cuttlefish:conf_get("kafka.port", Conf),
+  KafkaPartitionStrategy = cuttlefish:conf_get("kafka.partitionstrategy", Conf),
+  KafkaPartitionWorkers = cuttlefish:conf_get("kafka.partitionworkers", Conf),
+  KafkaPayloadTopic = cuttlefish:conf_get("kafka.payloadtopic", Conf),
+  [{host, KafkaHost}, {port, KafkaPort}, {partitionstrategy, KafkaPartitionStrategy}, {partitionworkers, KafkaPartitionWorkers}, {payloadtopic, KafkaPayloadTopic}]
+  end}.

+ 7 - 0
rebar.config

@@ -0,0 +1,7 @@
+{deps, [
+    {cuttlefish, {git, "https://github.com/emqx/cuttlefish.git",  {branch, "master"}}},
+    {ekaf, {git, "https://github.com/helpshift/ekaf.git",  {branch, "master"}}},
+    {emqx, {git, "https://github.com/emqx/emqx.git",  {branch, "master"}}}
+]}.
+
+{erl_opts, [debug_info]}.

+ 48 - 0
rebar.config.script

@@ -0,0 +1,48 @@
+%%-*- mode: erlang -*-
+%% emqx_management rebar.config.script
+
+CONFIG1 = case os:getenv("TRAVIS") of
+              "true" ->
+                  JobId = os:getenv("TRAVIS_JOB_ID"),
+                  [{coveralls_service_job_id, JobId},
+                   {coveralls_coverdata, "_build/test/cover/*.coverdata"},
+                   {coveralls_service_name , "travis-ci"} | CONFIG];
+              _ ->
+                  CONFIG
+          end,
+
+DEPS = case lists:keyfind(deps, 1, CONFIG1) of
+           {_, Deps} -> Deps;
+           _ -> []
+       end,
+
+CUR_BRANCH = case os:getenv("GITHUB_RUN_ID") of
+               false ->
+                 os:cmd("git branch | grep -e '^*' | cut -d' ' -f 2") -- "\n";
+               _ ->
+                 re:replace(os:getenv("GITHUB_REF"), ".*/", "", [global, {return ,list}])
+             end,
+
+MATCH_BRANCH = fun (BranchName) ->
+                 case re:run(BranchName, "^release|develop|master", [{capture, none}]) of
+                   match -> BranchName;
+                   _ -> case re:run(BranchName, "^[v]?[0-9]\.[0-9]\.([0-9]|(rc|beta|alpha)\.[0-9])", [{capture, none}]) of
+                          match -> BranchName;
+                          _ -> "develop"
+                        end
+                 end
+               end,
+
+BRANCH = MATCH_BRANCH(CUR_BRANCH),
+
+UrlPrefix = "https://github.com/emqx/",
+
+EMQX_DEP = {emqx, {git, UrlPrefix ++ "emqx", {branch, BRANCH}}},
+
+EMQX_RELOADER_DEP = {emqx_reloader, {git, UrlPrefix ++ "emqx-reloader", {branch, BRANCH}}},
+
+NewDeps = [EMQX_DEP, EMQX_RELOADER_DEP | DEPS],
+
+CONFIG2 = lists:keystore(deps, 1, CONFIG1, {deps, NewDeps}),
+
+CONFIG2.

+ 32 - 0
rebar3.crashdump

@@ -0,0 +1,32 @@
+Error: {badmatch,
+           {error,
+               {49,file,
+                {error,badarg,
+                    [{re,run,
+                         [[65288,38750,20998,25903,65289],
+                          "^release|develop|master",
+                          [{capture,none}]],
+                         []},
+                     {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,680}]},
+                     {erl_eval,expr,5,[{file,"erl_eval.erl"},{line,273}]},
+                     {erl_eval,expr,5,[{file,"erl_eval.erl"},{line,449}]},
+                     {erl_eval,exprs,5,[{file,"erl_eval.erl"},{line,126}]},
+                     {file,eval_stream2,6,[{file,"file.erl"},{line,1393}]},
+                     {file,script,2,[{file,"file.erl"},{line,1090}]},
+                     {rebar_config,consult_and_eval,2,
+                         [{file,"/tmp/cirrus-ci-build/src/rebar_config.erl"},
+                          {line,287}]}]}}}}
+[{rebar_config,consult_file_,1,
+               [{file,"/tmp/cirrus-ci-build/src/rebar_config.erl"},
+                {line,230}]},
+ {rebar_config,consult_file,1,
+               [{file,"/tmp/cirrus-ci-build/src/rebar_config.erl"},
+                {line,212}]},
+ {rebar3,init_config,0,
+         [{file,"/tmp/cirrus-ci-build/src/rebar3.erl"},{line,207}]},
+ {rebar3,run,1,[{file,"/tmp/cirrus-ci-build/src/rebar3.erl"},{line,100}]},
+ {rebar3,main,1,[{file,"/tmp/cirrus-ci-build/src/rebar3.erl"},{line,66}]},
+ {escript,run,2,[{file,"escript.erl"},{line,758}]},
+ {escript,start,1,[{file,"escript.erl"},{line,277}]},
+ {init,start_em,1,[]}]
+

+ 26 - 0
src/emqx_cli_demo_kafka.erl

@@ -0,0 +1,26 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2019 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_cli_demo_kafka).
+
+-export([cmd/1]).
+
+cmd(["arg1", "arg2"]) ->
+    emqx_ctl:print("ok");
+
+cmd(_) ->
+    emqx_ctl:usage([{"cmd arg1 arg2", "cmd demo"}]).
+

+ 14 - 0
src/emqx_plugin_kafka.app.src

@@ -0,0 +1,14 @@
+{application, emqx_plugin_kafka,
+ [{description, "EMQ X plugin kafka bridge, Edit by yzs-lyg"},
+  {vsn, "git"},
+  {modules, []},
+  {registered, [emqx_plugin_kafka_sup]},
+  {applications, [kernel,stdlib,ekaf]},
+  {mod, {emqx_plugin_kafka_app,[]}},
+  {env, []},
+  {licenses, ["Apache-2.0"]},
+  {maintainers, ["lyg <3613840847@qq.com>"]},
+  {links,[{"Github","https://github.com/yzs/emqx-plugin-kafka"}]}
+ ]
+}.
+

+ 25 - 0
src/emqx_plugin_kafka.app.src.script

@@ -0,0 +1,25 @@
+%%-*- mode: erlang -*-
+%% .app.src.script
+
+RemoveLeadingV =
+    fun(Tag) ->
+        case re:run(Tag, "v\[0-9\]+\.\[0-9\]+\.*") of
+            nomatch ->
+                Tag;
+            {match, _} ->
+                %% if it is a version number prefixed by 'v' then remove the 'v'
+                "v" ++ Vsn = Tag,
+                Vsn
+        end
+    end,
+
+case os:getenv("EMQX_DEPS_DEFAULT_VSN") of
+    false -> CONFIG; % env var not defined
+    []    -> CONFIG; % env var set to empty string
+    Tag ->
+       [begin
+           AppConf0 = lists:keystore(vsn, 1, AppConf, {vsn, RemoveLeadingV(Tag)}),
+           {application, App, AppConf0}
+        end || Conf = {application, App, AppConf} <- CONFIG]
+end.
+

+ 341 - 0
src/emqx_plugin_kafka.erl

@@ -0,0 +1,341 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2015-2017 Feng Lee <feng@emqtt.io>.
+%%
+%% Modified by Ramez Hanna <rhanna@iotblue.net>
+%% 
+%% 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_plugin_kafka).
+
+% -include("emqx_plugin_kafka.hrl").
+
+-include_lib("emqx/include/emqx.hrl").
+
+-export([load/1, unload/0]).
+
+%% Client Lifecircle Hooks
+-export([ on_client_connect/3
+        , on_client_connack/4
+        , on_client_connected/3
+        , on_client_disconnected/4
+        , on_client_authenticate/3
+        , on_client_check_acl/5
+        , on_client_subscribe/4
+        , on_client_unsubscribe/4
+        ]).
+
+%% Session Lifecircle Hooks
+-export([ on_session_created/3
+        , on_session_subscribed/4
+        , on_session_unsubscribed/4
+        , on_session_resumed/3
+        , on_session_discarded/3
+        , on_session_takeovered/3
+        , on_session_terminated/4
+        ]).
+
+%% Message Pubsub Hooks
+-export([ on_message_publish/2
+        , on_message_delivered/3
+        , on_message_acked/3
+        , on_message_dropped/4
+        ]).
+
+
+
+%% Called when the plugin application start
+load(Env) ->
+    ekaf_init([Env]), 
+    emqx:hook('client.connect',      {?MODULE, on_client_connect, [Env]}),
+    emqx:hook('client.connack',      {?MODULE, on_client_connack, [Env]}),
+    emqx:hook('client.connected',    {?MODULE, on_client_connected, [Env]}),
+    emqx:hook('client.disconnected', {?MODULE, on_client_disconnected, [Env]}),
+    emqx:hook('client.authenticate', {?MODULE, on_client_authenticate, [Env]}),
+    emqx:hook('client.check_acl',    {?MODULE, on_client_check_acl, [Env]}),
+    emqx:hook('client.subscribe',    {?MODULE, on_client_subscribe, [Env]}),
+    emqx:hook('client.unsubscribe',  {?MODULE, on_client_unsubscribe, [Env]}),
+    emqx:hook('session.created',     {?MODULE, on_session_created, [Env]}),
+    emqx:hook('session.subscribed',  {?MODULE, on_session_subscribed, [Env]}),
+    emqx:hook('session.unsubscribed',{?MODULE, on_session_unsubscribed, [Env]}),
+    emqx:hook('session.resumed',     {?MODULE, on_session_resumed, [Env]}),
+    emqx:hook('session.discarded',   {?MODULE, on_session_discarded, [Env]}),
+    emqx:hook('session.takeovered',  {?MODULE, on_session_takeovered, [Env]}),
+    emqx:hook('session.terminated',  {?MODULE, on_session_terminated, [Env]}),
+    emqx:hook('message.publish',     {?MODULE, on_message_publish, [Env]}),
+    emqx:hook('message.delivered',   {?MODULE, on_message_delivered, [Env]}),
+    emqx:hook('message.acked',       {?MODULE, on_message_acked, [Env]}),
+    emqx:hook('message.dropped',     {?MODULE, on_message_dropped, [Env]}).
+	
+on_client_connect(ConnInfo = #{clientid := ClientId}, Props, _Env) ->
+    %io:format("Client(~s) connect, ConnInfo: ~p, Props: ~p~n",
+              %[ClientId, ConnInfo, Props]),
+    {ok, Props}.
+
+on_client_connack(ConnInfo = #{clientid := ClientId}, Rc, Props, _Env) ->
+    %io:format("Client(~s) connack, ConnInfo: ~p, Rc: ~p, Props: ~p~n",
+              %[ClientId, ConnInfo, Rc, Props]),
+    {ok, Props}.
+	
+on_client_connected(ClientInfo = #{clientid := ClientId}, ConnInfo, _Env) ->
+    io:format("Client(~s) connected, ClientInfo:~n~p~n, ConnInfo:~n~p~n",
+              [ClientId, ClientInfo, ConnInfo]),
+		{IpAddr, _Port} = maps:get(peername, ConnInfo),
+    	Action = <<"connected">>,
+    	Now = calendar:datetime_to_gregorian_seconds(calendar:universal_time())-719528*24*3600,
+    	Online = 1,
+    	Payload = [
+		{action, Action}, 
+		{device_id, ClientId}, 
+		{username, maps:get(username, ClientInfo)},
+		{keepalive, maps:get(keepalive, ConnInfo)},
+		{ipaddress, iolist_to_binary(ntoa(IpAddr))},
+		{proto_name, maps:get(proto_name, ConnInfo)},
+		{proto_ver, maps:get(proto_ver, ConnInfo)},
+		{timestamp, Now},
+		{online, Online}
+    ],
+   produce_kafka_payload(Payload),
+   ok.
+
+on_client_disconnected(ClientInfo = #{clientid := ClientId}, ReasonCode, ConnInfo, _Env) ->
+    io:format("Client(~s) disconnected due to ~p, ClientInfo:~n~p~n, ConnInfo:~n~p~n",
+              [ClientId, ReasonCode, ClientInfo, ConnInfo]), 
+    Action = <<"disconnected">>,
+    Now = calendar:datetime_to_gregorian_seconds(calendar:universal_time())-719528*24*3600,
+    Online = 0,
+    Payload = [
+		{action, Action}, 
+		{device_id, ClientId}, 
+		{username, maps:get(username, ClientInfo)},
+ 		{reason, ReasonCode},
+		{timestamp, Now},
+		{online, Online}
+	],	
+    produce_kafka_payload(Payload),
+    ok.
+
+on_client_authenticate(_ClientInfo = #{clientid := ClientId}, Result, _Env) ->
+    io:format("Client(~s) authenticate, Result:~n~p~n", [ClientId, Result]),
+    {ok, Result}.
+
+on_client_check_acl(_ClientInfo = #{clientid := ClientId}, Topic, PubSub, Result, _Env) ->
+    io:format("Client(~s) check_acl, PubSub:~p, Topic:~p, Result:~p~n",
+              [ClientId, PubSub, Topic, Result]),
+    {ok, Result}.
+	
+%%---------------------------client subscribe start--------------------------%%
+on_client_subscribe(#{clientid := ClientId}, _Properties, TopicFilters, _Env) ->
+    io:format("Client(~s) will subscribe: ~p~n", [ClientId, TopicFilters]),  
+    Topic=erlang:element(1,erlang:hd(TopicFilters)),
+    Qos = erlang:element(2,lists:last(TopicFilters)),
+    Action = <<"subscribe">>,
+    Now = calendar:datetime_to_gregorian_seconds(calendar:universal_time())-719528*24*3600,
+    Payload = [
+		{device_id, ClientId},
+		{action, Action},  
+		{topic, Topic},
+		{qos, maps:get(qos, Qos)},
+		{timestamp, Now}
+	],	
+    produce_kafka_payload(Payload),
+	{ok, TopicFilters}.
+%%---------------------client subscribe stop----------------------%%
+on_client_unsubscribe(#{clientid := ClientId}, _Properties, TopicFilters, _Env) ->
+    io:format("Client(~s) will unsubscribe ~p~n", [ClientId, TopicFilters]),   
+	Topic=erlang:element(1,erlang:hd(TopicFilters)),
+    Action = <<"unsubscribe">>,
+    Now = calendar:datetime_to_gregorian_seconds(calendar:universal_time())-719528*24*3600,
+    Payload = [
+		{device_id, ClientId},
+		{action, Action},  
+		{topic, Topic},
+		{timestamp, Now}
+	],	
+    produce_kafka_payload(Payload),
+    {ok, TopicFilters}.
+
+on_message_dropped(#message{topic = <<"$SYS/", _/binary>>}, _By, _Reason, _Env) ->
+    ok;
+on_message_dropped(Message, _By = #{node := Node}, Reason, _Env) ->
+    io:format("Message dropped by node ~s due to ~s: ~s~n",
+              [Node, Reason, emqx_message:format(Message)]).
+
+	
+%%---------------------------message publish start--------------------------%%
+on_message_publish(Message = #message{topic = <<"$SYS/", _/binary>>}, _Env) ->
+    {ok, Message};
+on_message_publish(Message, _Env) ->
+    {ok, Payload} = format_payload(Message),
+    produce_kafka_payload(Payload),
+    {ok, Message}.	
+%%---------------------message publish stop----------------------%%
+
+on_message_delivered(_ClientInfo = #{clientid := ClientId}, Message, _Env) ->
+    io:format("Message delivered to client(~s): ~s~n",
+              [ClientId, emqx_message:format(Message)]),
+    Topic=Message#message.topic,
+    Payload=Message#message.payload,
+    Qos=Message#message.qos,
+    From=Message#message.from,
+    Timestamp=Message#message.timestamp,
+    Json = jsx:encode([
+     		{type,<<"deliver messqge">>},
+		{from,From},
+		{to,ClientId},
+		{topic,Topic},
+     	{payload,Payload},
+     	{qos,Qos},
+     	{cluster_node,node()},
+	{ts,now_secs(Timestamp)}
+	 ]),
+	EkafTopic=ekaf_get_topic(),
+	ekaf:produce_sync(EkafTopic,Json),
+ 	{ok, Message}.
+
+on_message_acked(_ClientInfo = #{clientid := ClientId}, Message, _Env) ->
+    io:format("Message acked by client(~s): ~s~n",
+              [ClientId, emqx_message:format(Message)]),
+    Topic=Message#message.topic,
+    Payload=Message#message.payload,
+    Qos=Message#message.qos,
+    From=Message#message.from,
+    Timestamp=Message#message.timestamp,
+    Json = jsx:encode([
+     	{type,<<"acked messqge">>},
+	{from,From},
+	{to,ClientId},
+	{topic,Topic},
+     	{payload,Payload},
+     	{qos,Qos},
+     	{cluster_node,node()},
+	{ts,now_secs(Timestamp)}
+	 ]),
+	EkafTopic=ekaf_get_topic(),
+	ekaf:produce_sync(EkafTopic,Json).
+	
+%%--------------------------------------------------------------------
+%% Session Lifecircle Hooks
+%%--------------------------------------------------------------------
+
+on_session_created(#{clientid := ClientId}, SessInfo, _Env) ->
+    %io:format("Session(~s) created, Session Info:~n~p~n", [ClientId, SessInfo]).
+     ok.
+	
+
+on_session_subscribed(#{clientid := ClientId}, Topic, SubOpts, _Env) ->
+    io:format("Session(~s) subscribed ~s with subopts: ~p~n", [ClientId, Topic, SubOpts]).
+
+on_session_unsubscribed(#{clientid := ClientId}, Topic, Opts, _Env) ->
+    io:format("Session(~s) unsubscribed ~s with opts: ~p~n", [ClientId, Topic, Opts]).
+
+on_session_resumed(#{clientid := ClientId}, SessInfo, _Env) ->
+    io:format("Session(~s) resumed, Session Info:~n~p~n", [ClientId, SessInfo]).
+
+on_session_discarded(_ClientInfo = #{clientid := ClientId}, SessInfo, _Env) ->
+    io:format("Session(~s) is discarded. Session Info: ~p~n", [ClientId, SessInfo]).
+
+on_session_takeovered(_ClientInfo = #{clientid := ClientId}, SessInfo, _Env) ->
+    io:format("Session(~s) is takeovered. Session Info: ~p~n", [ClientId, SessInfo]).
+
+on_session_terminated(_ClientInfo = #{clientid := ClientId}, Reason, SessInfo, _Env) ->
+    io:format("Session(~s) is terminated due to ~p~nSession Info: ~p~n",
+              [ClientId, Reason, SessInfo]).	
+ 	
+ekaf_init(_Env) ->
+    {ok, BrokerValues} = application:get_env(emqx_plugin_kafka, broker),
+    KafkaHost = proplists:get_value(host, BrokerValues),
+    io:format("KafkaHost = ~s~n",[KafkaHost]),
+    KafkaPort = proplists:get_value(port, BrokerValues),
+    io:format("KafkaPort = ~s~n",[KafkaPort]),
+    KafkaPartitionStrategy = proplists:get_value(partitionstrategy, BrokerValues),
+    KafkaPartitionWorkers = proplists:get_value(partitionworkers, BrokerValues),
+    KafkaTopic = proplists:get_value(payloadtopic, BrokerValues),
+    io:format("KafkaTopic = ~s~n",[KafkaTopic]),
+    application:set_env(ekaf, ekaf_bootstrap_broker, {KafkaHost, list_to_integer(KafkaPort)}),
+    application:set_env(ekaf, ekaf_partition_strategy, list_to_atom(KafkaPartitionStrategy)),
+    application:set_env(ekaf, ekaf_per_partition_workers, KafkaPartitionWorkers),
+    application:set_env(ekaf, ekaf_bootstrap_topics, list_to_binary(KafkaTopic)),
+    application:set_env(ekaf, ekaf_buffer_ttl, 10),
+    application:set_env(ekaf, ekaf_max_downtime_buffer_size, 5),
+    % {ok, _} = application:ensure_all_started(kafkamocker),
+    {ok, _} = application:ensure_all_started(gproc),
+    % {ok, _} = application:ensure_all_started(ranch),
+    {ok, _} = application:ensure_all_started(ekaf).
+
+ekaf_get_topic() ->
+    {ok, Topic} = application:get_env(ekaf, ekaf_bootstrap_topics),
+    Topic.
+
+
+format_payload(Message) ->
+    Username = emqx_message:get_header(username, Message),
+    Topic = Message#message.topic,
+    Tail = string:right(binary_to_list(Topic), 4),
+    RawType = string:equal(Tail, <<"_raw">>),
+    % io:format("Tail= ~s , RawType= ~s~n",[Tail,RawType]),
+
+    MsgPayload = Message#message.payload,
+    % io:format("MsgPayload : ~s~n", [MsgPayload]),
+    if
+        RawType == true ->
+            MsgPayload64 = list_to_binary(base64:encode_to_string(MsgPayload));
+    % io:format("MsgPayload64 : ~s~n", [MsgPayload64]);
+        RawType == false ->
+            MsgPayload64 = MsgPayload
+    end,
+    Payload = [{action, message_publish},
+        {device_id, Message#message.from},
+        {username, Username},
+        {topic, Topic},
+        {payload, MsgPayload64},
+        {ts, emqx_time:now_secs(Message#message.timestamp)}],
+
+    {ok, Payload}.
+
+
+%% Called when the plugin application stop
+unload() ->
+    emqx:unhook('client.connect',      {?MODULE, on_client_connect}),
+    emqx:unhook('client.connack',      {?MODULE, on_client_connack}),
+    emqx:unhook('client.connected',    {?MODULE, on_client_connected}),
+    emqx:unhook('client.disconnected', {?MODULE, on_client_disconnected}),
+    emqx:unhook('client.authenticate', {?MODULE, on_client_authenticate}),
+    emqx:unhook('client.check_acl',    {?MODULE, on_client_check_acl}),
+    emqx:unhook('client.subscribe',    {?MODULE, on_client_subscribe}),
+    emqx:unhook('client.unsubscribe',  {?MODULE, on_client_unsubscribe}),
+    emqx:unhook('session.created',     {?MODULE, on_session_created}),
+    emqx:unhook('session.subscribed',  {?MODULE, on_session_subscribed}),
+    emqx:unhook('session.unsubscribed',{?MODULE, on_session_unsubscribed}),
+    emqx:unhook('session.resumed',     {?MODULE, on_session_resumed}),
+    emqx:unhook('session.discarded',   {?MODULE, on_session_discarded}),
+    emqx:unhook('session.takeovered',  {?MODULE, on_session_takeovered}),
+    emqx:unhook('session.terminated',  {?MODULE, on_session_terminated}),
+    emqx:unhook('message.publish',     {?MODULE, on_message_publish}),
+    emqx:unhook('message.delivered',   {?MODULE, on_message_delivered}),
+    emqx:unhook('message.acked',       {?MODULE, on_message_acked}),
+    emqx:unhook('message.dropped',     {?MODULE, on_message_dropped}).
+
+produce_kafka_payload(Message) ->
+    Topic = ekaf_get_topic(),
+    {ok, MessageBody} = emqx_json:safe_encode(Message),
+    % io:format("Message = ~s~n",[MessageBody]),
+    Payload = iolist_to_binary(MessageBody),
+    ekaf:produce_async_batched(Topic, Payload).
+
+ntoa({0,0,0,0,0,16#ffff,AB,CD}) ->
+    inet_parse:ntoa({AB bsr 8, AB rem 256, CD bsr 8, CD rem 256});
+ntoa(IP) ->
+    inet_parse:ntoa(IP).
+now_secs({MegaSecs, Secs, _MicroSecs}) ->
+    MegaSecs * 1000000 + Secs.

+ 34 - 0
src/emqx_plugin_kafka_app.erl

@@ -0,0 +1,34 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2019 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_plugin_kafka_app).
+
+-behaviour(application).
+
+-emqx_plugin(?MODULE).
+
+-export([ start/2
+        , stop/1
+        ]).
+
+start(_StartType, _StartArgs) ->
+    {ok, Sup} = emqx_plugin_kafka_sup:start_link(),
+    emqx_plugin_kafka:load(application:get_all_env()),
+    {ok, Sup}.
+
+stop(_State) ->
+    emqx_plugin_kafka:unload().
+

+ 30 - 0
src/emqx_plugin_kafka_sup.erl

@@ -0,0 +1,30 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2019 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_plugin_kafka_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/0]).
+
+-export([init/1]).
+
+start_link() ->
+    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init([]) ->
+    {ok, { {one_for_all, 0, 1}, []} }.
+

+ 23 - 0
test/emqx_plugin_kafka_SUITE.erl

@@ -0,0 +1,23 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2019 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_plugin_kafka_SUITE).
+
+-compile(export_all).
+
+all() -> [].
+
+groups() -> [].