You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@eventmesh.apache.org by ch...@apache.org on 2023/06/20 02:21:07 UTC
[eventmesh-go] 01/33: transfer server-go from parent
This is an automated email from the ASF dual-hosted git repository.
chenguangsheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/eventmesh-go.git
commit 3b1cd0d5adfd832308463de6832451caa18adaca
Author: qqeasonchen <qq...@gmail.com>
AuthorDate: Tue Mar 28 17:23:01 2023 +0800
transfer server-go from parent
---
LICENSE | 201 +++
Makefile | 109 ++
README.md | 15 +
api/openapi-spec/swagger.json | 16 +
api/protobuf-spec/eventmesh.proto | 16 +
assets/REAEMD.md | 1 +
config/common.go | 24 +
config/config.go | 148 ++
config/config_test.go | 93 ++
config/grpc.go | 51 +
config/http.go | 25 +
config/plugin.go | 28 +
config/pprof.go | 25 +
config/tcp.go | 29 +
config/testdata/test_config.yaml | 87 ++
config/tls.go | 34 +
configs/eventmesh-server.yaml | 89 ++
deployments/README.md | 1 +
deployments/docker-compose/eventmesh.yml | 15 +
deployments/helms/REAME.md | 1 +
eventmesh.go | 67 +
examples/README.md | 1 +
examples/webhook/main.go | 84 ++
go.mod | 92 ++
go.sum | 694 +++++++++
log/config.go | 186 +++
log/log.go | 113 ++
log/logger.go | 120 ++
log/logger_factory.go | 155 ++
log/rollwriter/async_roll_writer.go | 133 ++
log/rollwriter/async_roll_writer_options.go | 62 +
log/rollwriter/roll_writer.go | 446 ++++++
log/rollwriter/roll_writer_options.go | 72 +
log/writer_factory.go | 116 ++
log/zaplogger.go | 502 +++++++
pkg/README.md | 1 +
pkg/common/protocol/grpc/keys.go | 65 +
pkg/common/protocol/grpc/message.go | 26 +
pkg/common/protocol/grpc/status_code.go | 68 +
pkg/common/protocol/tcp/pck.go | 97 ++
pkg/naming/registry/instance.go | 34 +
pkg/naming/registry/registry.go | 72 +
pkg/naming/selector/selector.go | 43 +
pkg/util/id.go | 39 +
pkg/util/id_test.go | 94 ++
pkg/util/ip.go | 43 +
pkg/util/pid.go | 32 +
pkg/util/pid_test.go | 37 +
pkg/version/version.go | 20 +
plugin/connector/connector.go | 122 ++
.../connector/rocketmq/client/rocketmq_consumer.go | 159 +++
.../connector/rocketmq/client/rocketmq_producer.go | 168 +++
plugin/connector/rocketmq/config/client_config.go | 41 +
.../rocketmq/constants/message_constants.go | 25 +
.../constants/rocketmq_message_properties.go | 61 +
plugin/connector/rocketmq/consumer.go | 221 +++
plugin/connector/rocketmq/consumer_test.go | 130 ++
.../rocketmq/convert/message_ext_convert.go | 33 +
.../rocketmq/convert/rocketmq_message_reader.go | 110 ++
.../convert/rocketmq_message_reader_test.go | 47 +
.../rocketmq/convert/rocketmq_message_writer.go | 86 ++
.../convert/rocketmq_message_writer_test.go | 44 +
plugin/connector/rocketmq/factory.go | 84 ++
.../rocketmq/mock/mock_rocketmq_consumer.go | 145 ++
.../rocketmq/mock/mock_rocketmq_producer.go | 167 +++
plugin/connector/rocketmq/producer.go | 236 ++++
plugin/connector/rocketmq/producer_test.go | 214 +++
plugin/connector/rocketmq/resource.go | 29 +
plugin/connector/rocketmq/utils/oms_util.go | 27 +
plugin/connector/standalone/broker.go | 259 ++++
plugin/connector/standalone/consumer.go | 200 +++
plugin/connector/standalone/factory.go | 70 +
plugin/connector/standalone/message.go | 47 +
plugin/connector/standalone/producer.go | 116 ++
plugin/connector/standalone/resource.go | 29 +
.../standalone/standalone_connector_test.go | 224 +++
plugin/database/mysql/mysql.go | 68 +
plugin/metrics/metrics.go | 36 +
plugin/metrics/prometheus/config.go | 26 +
plugin/metrics/prometheus/metrics.go | 153 ++
plugin/metrics/prometheus/metrics_test.go | 30 +
plugin/plugin.go | 66 +
plugin/protocol/cloudevents/Readme.md | 1 +
plugin/protocol/cloudevents/cloudevents.go | 215 +++
plugin/protocol/protocol.go | 40 +
plugin/registry/model.go | 42 +
plugin/registry/nacos/config.go | 24 +
.../nacos/mocks/mock_naming_client_interface.go | 211 +++
plugin/registry/nacos/nacos.go | 241 ++++
plugin/registry/nacos/nacos_test.go | 314 +++++
plugin/registry/registry.go | 30 +
plugin/setup.go | 153 ++
plugin/types.go | 37 +
runtime/consts/constant.go | 24 +
runtime/consts/env.go | 32 +
runtime/consts/grpc.go | 23 +
runtime/consts/state.go | 25 +
.../core/protocol/grpc/consumer/consumer_group.go | 74 +
.../grpc/consumer/consumer_group_client.go | 84 ++
.../grpc/consumer/consumer_group_option.go | 286 ++++
.../grpc/consumer/consumer_group_option_test.go | 116 ++
.../protocol/grpc/consumer/consumer_manager.go | 212 +++
.../grpc/consumer/consumer_manager_test.go | 367 +++++
.../core/protocol/grpc/consumer/consumer_mesh.go | 251 ++++
.../protocol/grpc/consumer/consumer_mesh_test.go | 224 +++
.../protocol/grpc/consumer/consumer_processor.go | 317 +++++
.../grpc/consumer/consumer_processor_test.go | 186 +++
.../protocol/grpc/consumer/consumer_service.go | 164 +++
.../grpc/consumer/consumer_service_test.go | 34 +
.../core/protocol/grpc/consumer/message_context.go | 31 +
.../core/protocol/grpc/consumer/message_handler.go | 108 ++
.../core/protocol/grpc/consumer/message_request.go | 248 ++++
.../grpc/consumer/mocks/consumer_group_option.go | 209 +++
.../grpc/consumer/mocks/consumer_manager.go | 148 ++
runtime/core/protocol/grpc/emitter/emitter.go | 43 +
.../core/protocol/grpc/emitter/mocks/emitter.go | 66 +
runtime/core/protocol/grpc/generate_mocks.sh | 27 +
.../protocol/grpc/heartbeat/heartbeat_processor.go | 74 +
.../protocol/grpc/heartbeat/heartbeat_service.go | 70 +
.../core/protocol/grpc/mocks/consumer_service.go | 105 ++
.../core/protocol/grpc/producer/message_context.go | 31 +
.../core/protocol/grpc/producer/producer_group.go | 20 +
.../protocol/grpc/producer/producer_manager.go | 84 ++
.../core/protocol/grpc/producer/producer_mesh.go | 110 ++
.../protocol/grpc/producer/producer_processor.go | 333 +++++
.../protocol/grpc/producer/producer_service.go | 124 ++
.../grpc/producer/producer_service_test.go | 86 ++
runtime/core/protocol/grpc/retry/retry.go | 33 +
runtime/core/protocol/grpc/validator/validator.go | 153 ++
runtime/core/wrapper/base.go | 44 +
runtime/core/wrapper/consumer.go | 96 ++
runtime/core/wrapper/consumer_test.go | 128 ++
runtime/core/wrapper/mocks/consumer.go | 146 ++
runtime/core/wrapper/producer.go | 79 ++
runtime/core/wrapper/producer_test.go | 79 ++
runtime/emserver/graceful.go | 37 +
runtime/emserver/grpc.go | 111 ++
runtime/emserver/grpc_test.go | 39 +
runtime/emserver/http.go | 81 ++
runtime/emserver/mocks/mock_graceful.go | 78 +
runtime/emserver/pprof.go | 61 +
runtime/emserver/pprof_test.go | 42 +
runtime/emserver/tcp.go | 38 +
runtime/proto/pb/README.md | 20 +
runtime/proto/pb/eventmesh-client.pb.go | 1485 ++++++++++++++++++++
runtime/proto/pb/eventmesh-client.proto | 165 +++
runtime/proto/pb/eventmesh-client_grpc.pb.go | 479 +++++++
runtime/proto/pb/mocks/consumer_service.go | 107 ++
runtime/proto/pb/mocks/heartbeat_service.go | 78 +
runtime/proto/pb/mocks/producer_service.go | 108 ++
runtime/registry/registry.go | 23 +
runtime/server.go | 101 ++
scripts/REAEMD.md | 1 +
style/checkStyle.xml | 354 +++++
tests/grpc.md | 199 +++
155 files changed, 18004 insertions(+)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/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.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5e9dcd7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,109 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+################################################################################
+# Variables #
+################################################################################
+export GO111MODULE ?= on
+export GOPROXY ?= https://proxy.golang.org
+export GOSUMDB ?= sum.golang.org
+
+# By default, disable CGO_ENABLED. See the details on https://golang.org/cmd/cgo
+CGO ?= 0
+
+LOCAL_ARCH := $(shell uname -m)
+ifeq ($(LOCAL_ARCH),x86_64)
+ TARGET_ARCH_LOCAL=amd64
+else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 5),armv8)
+ TARGET_ARCH_LOCAL=arm64
+else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 4),armv)
+ TARGET_ARCH_LOCAL=arm
+else
+ TARGET_ARCH_LOCAL=amd64
+endif
+export GOARCH ?= $(TARGET_ARCH_LOCAL)
+
+LOCAL_OS := $(shell uname)
+ifeq ($(LOCAL_OS),Linux)
+ TARGET_OS_LOCAL = linux
+else ifeq ($(LOCAL_OS),Darwin)
+ TARGET_OS_LOCAL = darwin
+else
+ TARGET_OS_LOCAL ?= windows
+endif
+export GOOS ?= $(TARGET_OS_LOCAL)
+
+ifeq ($(GOOS),windows)
+BINARY_EXT_LOCAL:=.exe
+GOLANGCI_LINT:=golangci-lint.exe
+BUILDMODE:=-buildmode=exe
+else
+BINARY_EXT_LOCAL:=
+GOLANGCI_LINT:=golangci-lint
+endif
+
+COVERAGE_OPTS = -covermode=set -coverprofile=cover_set.out
+
+
+################################################################################
+# Target: test #
+################################################################################
+.PHONY: test
+test:
+ go test ./...
+
+################################################################################
+# Target: build #
+################################################################################
+.PHONY: build
+build:
+ mkdir -p bin/ && go build -o ./bin
+
+################################################################################
+# Target: coverage #
+################################################################################
+.PHONY: coverage
+coverage:
+ go test ./... $(COVERAGE_OPTS)
+
+################################################################################
+# Target: lint #
+################################################################################
+.PHONY: lint
+lint:
+ $(GOLANGCI_LINT) run --timeout=20m
+
+################################################################################
+# Target: go.mod #
+################################################################################
+.PHONY: go.mod
+go.mod:
+ go mod tidy
+
+################################################################################
+# Target: check-diff #
+################################################################################
+.PHONY: check-diff
+check-diff:
+ git diff --exit-code ./go.mod # check no changes
+
+################################################################################
+# Target: clean #
+################################################################################
+.PHONY: clean
+clean:
+ @-rm -rf *.out
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ea5903e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+EventMesh Server Go
+- Makefile tip
+ >1. use golangci-lint static code check
+ >>- install: `go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest`
+ >>- `make lint`
+
+ >2. test code
+ >>- `make test`
+
+ >3. test coverage
+ >>- `make coverage`
+
+ >4. build code
+ >>- `make build`
+
diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json
new file mode 100644
index 0000000..d973dce
--- /dev/null
+++ b/api/openapi-spec/swagger.json
@@ -0,0 +1,16 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
\ No newline at end of file
diff --git a/api/protobuf-spec/eventmesh.proto b/api/protobuf-spec/eventmesh.proto
new file mode 100644
index 0000000..d973dce
--- /dev/null
+++ b/api/protobuf-spec/eventmesh.proto
@@ -0,0 +1,16 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
\ No newline at end of file
diff --git a/assets/REAEMD.md b/assets/REAEMD.md
new file mode 100644
index 0000000..cd9d81b
--- /dev/null
+++ b/assets/REAEMD.md
@@ -0,0 +1 @@
+include resource, such png and log
\ No newline at end of file
diff --git a/config/common.go b/config/common.go
new file mode 100644
index 0000000..400c221
--- /dev/null
+++ b/config/common.go
@@ -0,0 +1,24 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package config
+
+type Common struct {
+ Name string `yaml:"name" toml:"name"`
+ RegistryName string `yaml:"registry-name" toml:"registry-name"`
+ Cluster string `yaml:"cluster" toml:"cluster"`
+ Env string `yaml:"env" toml:"env"`
+ IDC string `yaml:"idc" toml:"idc"`
+}
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000..77d11f0
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,148 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package config
+
+import (
+ "io/ioutil"
+ "sync/atomic"
+ "time"
+
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+ "gopkg.in/yaml.v3"
+)
+
+// ServerConfigPath is the file path of server config file.
+var ServerConfigPath = defaultConfigPath
+
+const (
+ defaultConfigPath = "./configs/eventmesh-server.yaml"
+)
+
+type Config struct {
+ Common *Common `yaml:"common" toml:"common"`
+ Server struct {
+ *HTTPOption `yaml:"http" toml:"http"`
+ *GRPCOption `yaml:"grpc" toml:"grpc"`
+ *TCPOption `yaml:"tcp" toml:"tcp"`
+ }
+ PProf *PProfOption `yaml:"pprof" toml:"pprof"`
+ ActivePlugins map[string]string `yaml:"active-plugins" toml:"active-plugins"`
+ Plugins plugin.Config `yaml:"plugins,omitempty"`
+}
+
+var globalConfig atomic.Value
+
+func init() {
+ globalConfig.Store(defaultConfig())
+}
+
+func defaultConfig() *Config {
+ cfg := &Config{}
+ cfg.Common = &Common{
+ Name: "eventmesh-server",
+ RegistryName: "eventmesh-go",
+ Cluster: "1",
+ Env: "{}",
+ IDC: "idc1",
+ }
+ cfg.Server.GRPCOption = &GRPCOption{
+ Port: "10010",
+ TLSOption: &TLSOption{
+ EnableInsecure: false,
+ CA: "",
+ Certfile: "",
+ Keyfile: "",
+ },
+ SendPoolSize: 10,
+ SubscribePoolSize: 10,
+ RetryPoolSize: 10,
+ PushMessagePoolSize: 10,
+ ReplyPoolSize: 10,
+ MsgReqNumPerSecond: 5,
+ SessionExpiredInMills: 5 * time.Second,
+ SendMessageTimeout: 5 * time.Second,
+ }
+ cfg.Server.HTTPOption = &HTTPOption{
+ Port: "10010",
+ TLSOption: &TLSOption{
+ EnableInsecure: false,
+ CA: "",
+ Certfile: "",
+ Keyfile: "",
+ },
+ }
+ cfg.Server.TCPOption = &TCPOption{
+ Port: "10010",
+ TLSOption: &TLSOption{
+ EnableInsecure: false,
+ CA: "",
+ Certfile: "",
+ Keyfile: "",
+ },
+ Multicore: false,
+ }
+ cfg.ActivePlugins = map[string]string{
+ "connector": "standalone",
+ "log": "default",
+ }
+ cfg.PProf = &PProfOption{
+ Enable: true,
+ Port: "10011",
+ }
+ cfg.Plugins = map[string]map[string]yaml.Node{
+ "connector": {
+ "standalone": yaml.Node{},
+ },
+ }
+ return cfg
+}
+
+// GlobalConfig returns the global Config.
+func GlobalConfig() *Config {
+ return globalConfig.Load().(*Config)
+}
+
+// SetGlobalConfig set the global Config.
+func SetGlobalConfig(cfg *Config) {
+ globalConfig.Store(cfg)
+}
+
+// LoadGlobalConfig loads a Config from the config file path and sets it as the global Config.
+func LoadGlobalConfig(configPath string) error {
+ cfg, err := LoadConfig(configPath)
+ if err != nil {
+ return err
+ }
+ SetGlobalConfig(cfg)
+ return nil
+}
+
+// LoadConfig loads a Config from the config file path.
+func LoadConfig(configPath string) (*Config, error) {
+ return parseConfigFromFile(configPath)
+}
+
+func parseConfigFromFile(configPath string) (*Config, error) {
+ buf, err := ioutil.ReadFile(configPath)
+ if err != nil {
+ return nil, err
+ }
+ cfg := defaultConfig()
+ if err := yaml.Unmarshal(buf, cfg); err != nil {
+ return nil, err
+ }
+ return cfg, nil
+}
diff --git a/config/config_test.go b/config/config_test.go
new file mode 100644
index 0000000..0e91a1a
--- /dev/null
+++ b/config/config_test.go
@@ -0,0 +1,93 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package config
+
+import (
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+ testifyassert "github.com/stretchr/testify/assert"
+ "gopkg.in/yaml.v3"
+ "os"
+ "testing"
+ "time"
+)
+
+func TestConfig_Load(t *testing.T) {
+ assert := testifyassert.New(t)
+
+ config := &Config{}
+ config.Common = &Common{
+ RegistryName: "test",
+ Cluster: "test",
+ Env: "env",
+ IDC: "idc1",
+ }
+ config.Server.GRPCOption = &GRPCOption{
+ Port: "10010",
+ TLSOption: &TLSOption{
+ EnableInsecure: false,
+ CA: "",
+ Certfile: "",
+ Keyfile: "",
+ },
+ SendPoolSize: 10,
+ SubscribePoolSize: 10,
+ RetryPoolSize: 10,
+ PushMessagePoolSize: 10,
+ ReplyPoolSize: 10,
+ MsgReqNumPerSecond: 5,
+ SessionExpiredInMills: 5 * time.Second,
+ SendMessageTimeout: 5 * time.Second,
+ }
+ config.Server.HTTPOption = &HTTPOption{
+ Port: "10010",
+ TLSOption: &TLSOption{
+ EnableInsecure: false,
+ CA: "",
+ Certfile: "",
+ Keyfile: "",
+ },
+ }
+ config.Server.TCPOption = &TCPOption{
+ Port: "10010",
+ TLSOption: &TLSOption{
+ EnableInsecure: false,
+ CA: "",
+ Certfile: "",
+ Keyfile: "",
+ },
+ Multicore: false,
+ }
+ config.ActivePlugins = map[string]string{
+ "registry": "nacos",
+ "connector": "rocketmq",
+ "log": "default",
+ }
+ config.PProf = &PProfOption{
+ Enable: true,
+ Port: "10011",
+ }
+ config.Plugins = plugin.Config{}
+
+ configYAML := &Config{}
+ contentYAML, err := os.ReadFile("./testdata/test_config.yaml")
+ assert.NoError(err)
+ if err := yaml.Unmarshal(contentYAML, &configYAML); err != nil {
+ t.Fatal(err)
+ }
+ configYAML.Plugins = plugin.Config{}
+
+ assert.EqualValues(configYAML, config)
+}
diff --git a/config/grpc.go b/config/grpc.go
new file mode 100644
index 0000000..12073eb
--- /dev/null
+++ b/config/grpc.go
@@ -0,0 +1,51 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package config
+
+import "time"
+
+// GRPCOption configuratin for grpc server
+type GRPCOption struct {
+ Port string `yaml:"port" toml:"port"`
+
+ // TLSOption process with the tls configuration
+ *TLSOption `yaml:"tls" toml:"tls"`
+
+ // SendPoolSize pool in handle send msg
+ // default to 10
+ SendPoolSize int `yaml:"send-pool-size" toml:"send-pool-size"`
+ // SubscribePoolSize pool in handle subscribe msg
+ // default to 10
+ SubscribePoolSize int `yaml:"subscribe-pool-size" toml:"subscribe-pool-size"`
+ // RetryPoolSize pool in handle retry msg
+ // default to 10
+ RetryPoolSize int `yaml:"retry-pool-size" toml:"retry-pool-size"`
+ // PushMessagePoolSize pool to push message
+ // default to 10
+ PushMessagePoolSize int `yaml:"push-message-pool-size" toml:"push-message-pool-size"`
+ // ReplyPoolSize pool in handle reply msg
+ // default to 10
+ ReplyPoolSize int `yaml:"reply-pool-size" toml:"reply-pool-size"`
+
+ //MsgReqNumPerSecond
+ MsgReqNumPerSecond float64 `yaml:"msg-req-num-per-second" toml:"msg-req-num-per-second"`
+
+ // SessionExpiredInMills internal to clean the not work session consumer
+ SessionExpiredInMills time.Duration `yaml:"session-expired-in-mills"`
+ // SendMessageTimeout timeout in send message
+ // default to 5s
+ SendMessageTimeout time.Duration `yaml:"send-message-timeout"`
+}
diff --git a/config/http.go b/config/http.go
new file mode 100644
index 0000000..8f6fcd5
--- /dev/null
+++ b/config/http.go
@@ -0,0 +1,25 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package config
+
+// HTTPOption option for http/https server
+type HTTPOption struct {
+ // Port http server listen
+ Port string `yaml:"port" toml:"port"`
+
+ // TLSOption process with the tls configuration
+ *TLSOption `yaml:"tls" toml:"tls"`
+}
diff --git a/config/plugin.go b/config/plugin.go
new file mode 100644
index 0000000..0d399a8
--- /dev/null
+++ b/config/plugin.go
@@ -0,0 +1,28 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package config
+
+var (
+ // ConnectorPluginType plugin type for connector
+ // support standalone and rocketmq
+ // default to standalone
+ ConnectorPluginType = "connector"
+
+ // RegistryPluginType plugin type for connector
+ // support nacos and etcd
+ // default to nacos
+ RegistryPluginType = "registry"
+)
diff --git a/config/pprof.go b/config/pprof.go
new file mode 100644
index 0000000..b328c52
--- /dev/null
+++ b/config/pprof.go
@@ -0,0 +1,25 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package config
+
+// PProfOption option to start the prof
+type PProfOption struct {
+ // Enable the pprof or not
+ Enable bool `yaml:"enable" toml:"enable"`
+ // Port pprof server listen on
+ Port string `yaml:"port" toml:"port"`
+ *TLSOption `yaml:"tls" toml:"tls"`
+}
diff --git a/config/tcp.go b/config/tcp.go
new file mode 100644
index 0000000..7de9d8c
--- /dev/null
+++ b/config/tcp.go
@@ -0,0 +1,29 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package config
+
+// TCPOption option for eventmesh tcp server
+type TCPOption struct {
+ // Port http server listen
+ Port string `yaml:"port" toml:"port"`
+
+ // Multicore indicates whether the engine will
+ // be effectively created with multi-cores
+ Multicore bool `yaml:"multicore" toml:"multicore"`
+
+ // TLSOption process with the tls configuration
+ *TLSOption `yaml:"tls" toml:"tls"`
+}
diff --git a/config/testdata/test_config.yaml b/config/testdata/test_config.yaml
new file mode 100644
index 0000000..6527c71
--- /dev/null
+++ b/config/testdata/test_config.yaml
@@ -0,0 +1,87 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+common:
+ cluster: "test"
+ registry-name: "test"
+ env: "env"
+ idc: "idc1"
+server:
+ grpc:
+ port: 10010
+ tls:
+ enable-secure: false
+ ca: ""
+ certfile: ""
+ keyfile: ""
+ send-pool-size: 10
+ subscribe-pool-size: 10
+ retry-pool-size: 10
+ push-message-pool-size: 10
+ reply-pool-size: 10
+ msg-req-num-per-second: 5
+ session-expired-in-mills: 5s
+ send-message-timeout: 5s
+ http:
+ port: 10010
+ tls:
+ enable-secure: false
+ ca: ""
+ certfile: ""
+ keyfile: ""
+ tcp:
+ port: 10010
+ tls:
+ enable-secure: false
+ ca: ""
+ certfile: ""
+ keyfile: ""
+pprof:
+ enable: true
+ port: 10011
+active-plugins:
+ registry: nacos
+ connector: rocketmq
+ log: default
+plugins:
+ registry:
+ nacos:
+ address_list: your-nacos-addr
+ cache-dir: your-nacos-cache-dir
+ connector:
+ standalone:
+ rocketmq:
+ access_points: your-nameserver-addr
+ namespace: grpcnamespace
+ instance_name: grpcinstance
+ group_name: grpcgroupname
+ send_msg_timeout: 5000
+ producer_retry_times: 3
+ compress_msg_body_threshold: 1024
+ consumer_group: grpcconsumergroup
+ max_reconsume_times: 3
+ message_model: CLUSTERING
+ log:
+ default:
+ - writer: console
+ level: debug
+ - writer: file
+ level: info
+ writer_config:
+ filename: ./eventmesh.log
+ max_size: 10
+ max_backups: 10
+ max_age: 7
+ compress: false
\ No newline at end of file
diff --git a/config/tls.go b/config/tls.go
new file mode 100644
index 0000000..a167baf
--- /dev/null
+++ b/config/tls.go
@@ -0,0 +1,34 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package config
+
+// TLSOption option to tls
+type TLSOption struct {
+ // EnableInsecure enable the insecure request
+ EnableInsecure bool `yaml:"enable-secure" toml:"enable-secure"`
+
+ // CA no client authentication is used,
+ // and the file CA is used to verify the server certificate
+ CA string `yaml:"ca" toml:"ca"`
+
+ // Certfile client authentication is used with the specified cert/key pair.
+ // The server certificate is verified with the system CAs
+ Certfile string `yaml:"certfile" toml:"certfile"`
+
+ // client authentication is used with the specified cert/key pair.
+ // The server certificate is verified using the specified CA file
+ Keyfile string `yaml:"keyfile" toml:"keyfile"`
+}
diff --git a/configs/eventmesh-server.yaml b/configs/eventmesh-server.yaml
new file mode 100644
index 0000000..01d776a
--- /dev/null
+++ b/configs/eventmesh-server.yaml
@@ -0,0 +1,89 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+server:
+ grpc:
+ port: 10010
+ tls:
+ enable-secure: false
+ ca: ""
+ certfile: ""
+ keyfile: ""
+ pprof:
+ port: 10011
+ send-pool-size: 10
+ subscribe-pool-size: 10
+ retry-pool-size: 10
+ push-message-pool-size: 10
+ reply-pool-size: 10
+ msg-req-num-per-second: 5
+ cluster: "test"
+ env: "env"
+ idc: "idc1"
+ session-expired-in-mills: 5s
+ send-message-timeout: 5s
+ http:
+ port: 10010
+ tls:
+ enable-secure: false
+ ca: ""
+ certfile: ""
+ keyfile: ""
+ pprof:
+ port: 10011
+ tcp:
+ port: 10010
+ tls:
+ enable-secure: false
+ ca: ""
+ certfile: ""
+ keyfile: ""
+ pprof:
+ port: 10011
+active-plugins:
+ registry: nacos
+ connector: standalone
+ log: default
+plugins:
+ registry:
+ nacos:
+ address_list: your-nacos-addr
+ cache-dir: your-nacos-cache-dir
+ connector:
+ standalone:
+ rocketmq:
+ access_points: your-nameserver-addr
+ namespace: grpcnamespace
+ instance_name: grpcinstance
+ group_name: grpcgroupname
+ send_msg_timeout: 5000
+ producer_retry_times: 3
+ compress_msg_body_threshold: 1024
+ consumer_group: grpcconsumergroup
+ max_reconsume_times: 3
+ message_model: CLUSTERING
+ log:
+ default:
+ - writer: console
+ level: debug
+ - writer: file
+ level: info
+ writer_config:
+ filename: ./eventmesh.log
+ max_size: 10
+ max_backups: 10
+ max_age: 7
+ compress: false
\ No newline at end of file
diff --git a/deployments/README.md b/deployments/README.md
new file mode 100644
index 0000000..25c9ca2
--- /dev/null
+++ b/deployments/README.md
@@ -0,0 +1 @@
+docker-compose,kubernetes/helm,mesos,terraform,bosh
\ No newline at end of file
diff --git a/deployments/docker-compose/eventmesh.yml b/deployments/docker-compose/eventmesh.yml
new file mode 100644
index 0000000..4f1421a
--- /dev/null
+++ b/deployments/docker-compose/eventmesh.yml
@@ -0,0 +1,15 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
\ No newline at end of file
diff --git a/deployments/helms/REAME.md b/deployments/helms/REAME.md
new file mode 100644
index 0000000..ee342d9
--- /dev/null
+++ b/deployments/helms/REAME.md
@@ -0,0 +1 @@
+helms to deploy eventmesh
\ No newline at end of file
diff --git a/eventmesh.go b/eventmesh.go
new file mode 100644
index 0000000..046b0ce
--- /dev/null
+++ b/eventmesh.go
@@ -0,0 +1,67 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package main
+
+import (
+ "flag"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/config"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/log"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/runtime"
+
+ // load all plugin here if you add new plugin you need to import it by _
+ _ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq"
+ _ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/standalone"
+ _ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/database/mysql"
+ _ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/metrics/prometheus"
+ _ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/protocol/cloudevents"
+ _ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/registry/nacos"
+)
+
+var confPath string
+
+func init() {
+ flag.StringVar(&confPath, "config", config.ServerConfigPath, "configuration file path")
+}
+
+func main() {
+ cfg, err := config.LoadConfig(confPath)
+ if err != nil {
+ log.Fatalf("load config err:%v", err)
+ }
+ if err := SetupPlugins(cfg); err != nil {
+ log.Fatalf("setup plugin err:%v", err)
+ }
+ config.SetGlobalConfig(cfg)
+
+ if err := runtime.Start(); err != nil {
+ log.Fatalf("start runtime server err:%v", err)
+ }
+
+ log.Infof("stop runtime server success")
+}
+
+// SetupPlugins registers client config and setups plugins according to the Config.
+func SetupPlugins(cfg *config.Config) error {
+ // SetupConfig all plugins
+ if cfg.Plugins != nil {
+ if err := cfg.Plugins.Setup(); err != nil {
+ return err
+ }
+ }
+ plugin.SetActivePlugin(cfg.ActivePlugins)
+ return nil
+}
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..413c0f9
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1 @@
+how to use eventmesh
\ No newline at end of file
diff --git a/examples/webhook/main.go b/examples/webhook/main.go
new file mode 100644
index 0000000..97f6784
--- /dev/null
+++ b/examples/webhook/main.go
@@ -0,0 +1,84 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "github.com/gin-gonic/gin"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "strings"
+)
+
+// Response indicate the http response
+type Response struct {
+ RetCode string `json:"retCode"`
+ ErrMsg string `json:"errMsg"`
+}
+
+// OK return the success response
+func OK(data interface{}) *Response {
+ return &Response{
+ RetCode: "0",
+ ErrMsg: "OK",
+ }
+}
+
+// Err return the error response
+func Err(err error, code string) *Response {
+ return &Response{
+ RetCode: code,
+ ErrMsg: err.Error(),
+ }
+}
+
+// provide a webserver to handle the operator event callback
+func main() {
+ flag.Parse()
+ router := gin.Default()
+ printAndReply := func(c *gin.Context) {
+ buf, err := ioutil.ReadAll(c.Request.Body)
+ if err != nil {
+ c.JSON(http.StatusOK, Err(err, "-1"))
+ return
+ }
+ content, err := url.QueryUnescape(string(buf))
+ if err != nil {
+ c.JSON(http.StatusOK, Err(err, "-1"))
+ return
+ }
+
+ fmt.Printf("query content: %s \n", contentEscape(content))
+ c.JSON(http.StatusOK, OK("OK"))
+ }
+ router.Any("/*anypath", func(c *gin.Context) {
+ printAndReply(c)
+ })
+
+ fmt.Println("server start at:18080")
+ // if the web server start failed, we need to stop the server right now
+ if err := router.Run(fmt.Sprintf(":%d", 18080)); err != nil {
+ panic(err)
+ }
+}
+
+func contentEscape(content string) string {
+ escapedContent := strings.Replace(content, "\n", "", -1)
+ escapedContent = strings.Replace(escapedContent, "\r", "", -1)
+ return escapedContent
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..373d648
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,92 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 github.com/apache/incubator-eventmesh/eventmesh-server-go
+
+go 1.18
+
+require (
+ github.com/apache/rocketmq-client-go/v2 v2.1.1
+ github.com/cloudevents/sdk-go/v2 v2.11.0
+ github.com/deckarep/golang-set/v2 v2.1.0
+ github.com/gin-gonic/gin v1.8.1
+ github.com/gogf/gf v1.16.9
+ github.com/golang/mock v1.6.0
+ github.com/google/uuid v1.1.2
+ github.com/hashicorp/go-multierror v1.1.1
+ github.com/json-iterator/go v1.1.12
+ github.com/lestrrat-go/strftime v1.0.6
+ github.com/liyue201/gostl v1.0.1
+ github.com/nacos-group/nacos-sdk-go/v2 v2.1.2
+ github.com/panjf2000/ants/v2 v2.6.0
+ github.com/pkg/errors v0.9.1
+ github.com/prometheus/client_golang v1.13.0
+ github.com/stretchr/testify v1.8.0
+ github.com/unrolled/secure v1.12.0
+ go.uber.org/atomic v1.7.0
+ go.uber.org/fx v1.18.1
+ go.uber.org/zap v1.22.0
+ google.golang.org/grpc v1.48.0
+ google.golang.org/protobuf v1.28.1
+ gopkg.in/yaml.v3 v3.0.1
+)
+
+require (
+ github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/buger/jsonparser v1.1.1 // indirect
+ github.com/cespare/xxhash/v2 v2.1.2 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/emirpasic/gods v1.12.0 // indirect
+ github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/go-playground/locales v0.14.0 // indirect
+ github.com/go-playground/universal-translator v0.18.0 // indirect
+ github.com/go-playground/validator/v10 v10.10.0 // indirect
+ github.com/goccy/go-json v0.9.7 // indirect
+ github.com/golang/protobuf v1.5.2 // indirect
+ github.com/hashicorp/errwrap v1.0.0 // indirect
+ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af // indirect
+ github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
+ github.com/leodido/go-urn v1.2.1 // indirect
+ github.com/mattn/go-isatty v0.0.14 // indirect
+ github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
+ github.com/pelletier/go-toml/v2 v2.0.1 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/prometheus/client_model v0.2.0 // indirect
+ github.com/prometheus/common v0.37.0 // indirect
+ github.com/prometheus/procfs v0.8.0 // indirect
+ github.com/satori/go.uuid v1.2.0 // indirect
+ github.com/sirupsen/logrus v1.6.0 // indirect
+ github.com/stathat/consistent v1.0.0 // indirect
+ github.com/tidwall/gjson v1.13.0 // indirect
+ github.com/tidwall/match v1.1.1 // indirect
+ github.com/tidwall/pretty v1.2.0 // indirect
+ github.com/ugorji/go/codec v1.2.7 // indirect
+ go.uber.org/dig v1.15.0 // indirect
+ go.uber.org/multierr v1.6.0 // indirect
+ golang.org/x/crypto v0.5.0 // indirect
+ golang.org/x/net v0.7.0 // indirect
+ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
+ golang.org/x/sys v0.5.0 // indirect
+ golang.org/x/text v0.7.0 // indirect
+ golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
+ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect
+ gopkg.in/ini.v1 v1.66.2 // indirect
+ gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..7232fb0
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,694 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 h1:PpfENOj/vPfhhy9N2OFRjpue0hjM5XqAp2thFmkXXIk=
+github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/apache/rocketmq-client-go/v2 v2.1.1 h1:WY/LkOYSQaVyV+HOqdiIgF4LE3beZ/jwdSLKZlzpabw=
+github.com/apache/rocketmq-client-go/v2 v2.1.1/go.mod h1:GZzExtXY9zpI6FfiVJYAhw2IXQtgnHUuWpULo7nr5lw=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 h1:LdXxtjzvZYhhUaonAaAKArG3pyC67kGL3YY+6hGG8G4=
+github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudevents/sdk-go/v2 v2.11.0 h1:pCb7Cdkb8XpUoil+miuw6PEzuCG9cc8Erj8y1/q3odo=
+github.com/cloudevents/sdk-go/v2 v2.11.0/go.mod h1:xDmKfzNjM8gBvjaF8ijFjM1VYOVUEeUfapHMUX1T5To=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
+github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
+github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
+github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
+github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
+github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
+github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
+github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
+github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
+github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
+github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
+github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
+github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/gogf/gf v1.16.9 h1:Q803UmmRo59+Ws08sMVFOcd8oNpkSWL9vS33hlo/Cyk=
+github.com/gogf/gf v1.16.9/go.mod h1:8Q/kw05nlVRp+4vv7XASBsMe9L1tsVKiGoeP2AHnlkk=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
+github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc=
+github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
+github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
+github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
+github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
+github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
+github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
+github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
+github.com/liyue201/gostl v1.0.1 h1:VQdvogZ90WpCb5WdG9UxS6r5ulnYEp8VfEMEZXVtpIs=
+github.com/liyue201/gostl v1.0.1/go.mod h1:rgK+T1a0sQ1+CsAonfuD1J8C4iuGfOU9VAt9mmR/m10=
+github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/nacos-group/nacos-sdk-go/v2 v2.1.2 h1:A8GV6j0rw80I6tTKSav/pTpEgNECYXeFvZCsiLBWGnQ=
+github.com/nacos-group/nacos-sdk-go/v2 v2.1.2/go.mod h1:ys/1adWeKXXzbNWfRNbaFlX/t6HVLWdpsNDvmoWTw0g=
+github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/panjf2000/ants/v2 v2.6.0 h1:xOSpw42m+BMiJ2I33we7h6fYzG4DAlpE1xyI7VS2gxU=
+github.com/panjf2000/ants/v2 v2.6.0/go.mod h1:cU93usDlihJZ5CfRGNDYsiBYvoilLvBF5Qp/BT2GNRE=
+github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
+github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
+github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
+github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
+github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
+github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
+github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
+github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
+github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
+github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/stathat/consistent v1.0.0 h1:ZFJ1QTRn8npNBKW065raSZ8xfOqhpb8vLOkfp4CcL/U=
+github.com/stathat/consistent v1.0.0/go.mod h1:uajTPbgSygZBJ+V+0mY7meZ8i0XAcZs7AQ6V121XSxw=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/tidwall/gjson v1.13.0 h1:3TFY9yxOQShrvmjdM76K+jc66zJeT6D3/VFFYCGQf7M=
+github.com/tidwall/gjson v1.13.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
+github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
+github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
+github.com/unrolled/secure v1.12.0 h1:7k3jcgLwfjiKkhQde6VbQ3D4KDLtDBqDd/hs3PPANDY=
+github.com/unrolled/secure v1.12.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opentelemetry.io/otel v1.0.0 h1:qTTn6x71GVBvoafHK/yaRUmFzI4LcONZD0/kXxl5PHI=
+go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg=
+go.opentelemetry.io/otel/trace v1.0.0 h1:TSBr8GTEtKevYMG/2d21M989r5WJYVimhTHBKVEZuh4=
+go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE=
+go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM=
+go.uber.org/fx v1.18.1 h1:I7VWkdv4iKcbpH7KVSi9Fe1LGmpJv+pbBIb9NidPb+E=
+go.uber.org/fx v1.18.1/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY=
+go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+go.uber.org/zap v1.22.0 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0=
+go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
+golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
+golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w=
+google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
+gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+stathat.com/c/consistent v1.0.0 h1:ezyc51EGcRPJUxfHGSgJjWzJdj3NiMU9pNfLNGiXV0c=
+stathat.com/c/consistent v1.0.0/go.mod h1:QkzMWzcbB+yQBL2AttO6sgsQS/JSTapcDISJalmCDS0=
diff --git a/log/config.go b/log/config.go
new file mode 100644
index 0000000..abb63ef
--- /dev/null
+++ b/log/config.go
@@ -0,0 +1,186 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package log
+
+import (
+ "time"
+
+ "gopkg.in/yaml.v3"
+)
+
+// output name, default support console and file.
+const (
+ OutputConsole = "console"
+ OutputFile = "file"
+)
+
+// Config is the log config. Each log may have multiple outputs.
+type Config []OutputConfig
+
+// OutputConfig is the output config, includes console, file and remote.
+type OutputConfig struct {
+ // Writer is the output of log, such as console or file.
+ Writer string
+ WriteConfig WriteConfig `yaml:"writer_config"`
+
+ // Formatter is the format of log, such as console or json.
+ Formatter string
+ FormatConfig FormatConfig `yaml:"formatter_config"`
+
+ // RemoteConfig is the remote config. It's defined by business and should be registered by
+ // third-party modules.
+ RemoteConfig yaml.Node `yaml:"remote_config"`
+
+ // Level controls the log level, like debug, info or error.
+ Level string
+
+ // CallerSkip controls the nesting depth of log function.
+ CallerSkip int `yaml:"caller_skip"`
+}
+
+// WriteConfig is the local file config.
+type WriteConfig struct {
+ // LogPath is the log path
+ LogPath string `yaml:"log_path"`
+ // Filename is the file name.
+ Filename string `yaml:"filename"`
+ // WriteMode is the log write mod. 1: sync, 2: async, 3: fast(maybe dropped).
+ WriteMode int `yaml:"write_mode"`
+ // RollType is the log rolling type. Split files by size/time, default by size.
+ RollType string `yaml:"roll_type"`
+ // MaxAge is the max expire times(day).
+ MaxAge int `yaml:"max_age"`
+ // MaxBackups is the max backup files.
+ MaxBackups int `yaml:"max_backups"`
+ // Compress defines whether log should be compressed.
+ Compress bool `yaml:"compress"`
+ // MaxSize is the max size of log file(MB).
+ MaxSize int `yaml:"max_size"`
+
+ // TimeUnit splits files by time unit, like year/month/hour/minute, default day.
+ // It takes effect only when split by time.
+ TimeUnit TimeUnit `yaml:"time_unit"`
+}
+
+// FormatConfig is the log format config.
+type FormatConfig struct {
+ // TimeFmt is the time format of log output, default as "2006-01-02 15:04:05.000" on empty.
+ TimeFmt string `yaml:"time_fmt"`
+
+ // TimeKey is the time key of log output, default as "T".
+ TimeKey string `yaml:"time_key"`
+ // LevelKey is the level key of log output, default as "L".
+ LevelKey string `yaml:"level_key"`
+ // NameKey is the name key of log output, default as "N".
+ NameKey string `yaml:"name_key"`
+ // CallerKey is the caller key of log output, default as "C".
+ CallerKey string `yaml:"caller_key"`
+ // FunctionKey is the function key of log output, default as "", which means not to print
+ // function name.
+ FunctionKey string `yaml:"function_key"`
+ // MessageKey is the message key of log output, default as "M".
+ MessageKey string `yaml:"message_key"`
+ // StackTraceKey is the stack trace key of log output, default as "S".
+ StacktraceKey string `yaml:"stacktrace_key"`
+}
+
+// WriteMode is the log write mode, one of 1, 2, 3.
+type WriteMode int
+
+const (
+ // WriteSync writes synchronously.
+ WriteSync = 1
+ // WriteAsync writes asynchronously.
+ WriteAsync = 2
+ // WriteFast writes fast(may drop logs asynchronously).
+ WriteFast = 3
+)
+
+// By which log rolls.
+const (
+ // RollBySize rolls logs by file size.
+ RollBySize = "size"
+ // RollByTime rolls logs by time.
+ RollByTime = "time"
+)
+
+// Some common used time formats.
+const (
+ // TimeFormatMinute is accurate to the minute.
+ TimeFormatMinute = "%Y%m%d%H%M"
+ // TimeFormatHour is accurate to the hour.
+ TimeFormatHour = "%Y%m%d%H"
+ // TimeFormatDay is accurate to the day.
+ TimeFormatDay = "%Y%m%d"
+ // TimeFormatMonth is accurate to the month.
+ TimeFormatMonth = "%Y%m"
+ // TimeFormatYear is accurate to the year.
+ TimeFormatYear = "%Y"
+)
+
+// TimeUnit is the time unit by which files are split, one of minute/hour/day/month/year.
+type TimeUnit string
+
+const (
+ // Minute splits by the minute.
+ Minute = "minute"
+ // Hour splits by the hour.
+ Hour = "hour"
+ // Day splits by the day.
+ Day = "day"
+ // Month splits by the month.
+ Month = "month"
+ // Year splits by the year.
+ Year = "year"
+)
+
+// Format returns a string preceding with `.`. Use TimeFormatDay as default.
+func (t TimeUnit) Format() string {
+ var timeFmt string
+ switch t {
+ case Minute:
+ timeFmt = TimeFormatMinute
+ case Hour:
+ timeFmt = TimeFormatHour
+ case Day:
+ timeFmt = TimeFormatDay
+ case Month:
+ timeFmt = TimeFormatMonth
+ case Year:
+ timeFmt = TimeFormatYear
+ default:
+ timeFmt = TimeFormatDay
+ }
+ return "." + timeFmt
+}
+
+// RotationGap returns the time.Duration for time unit. Use one day as the default.
+func (t TimeUnit) RotationGap() time.Duration {
+ switch t {
+ case Minute:
+ return time.Minute
+ case Hour:
+ return time.Hour
+ case Day:
+ return time.Hour * 24
+ case Month:
+ return time.Hour * 24 * 30
+ case Year:
+ return time.Hour * 24 * 365
+ default:
+ return time.Hour * 24
+ }
+}
diff --git a/log/log.go b/log/log.go
new file mode 100644
index 0000000..6cb84e2
--- /dev/null
+++ b/log/log.go
@@ -0,0 +1,113 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// Package log provides a log for the framework and applications.
+package log
+
+import (
+ "fmt"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+// SetLevel sets log level for different output which may be "0", "1" or "2".
+func SetLevel(output string, level Level) {
+ GetDefaultLogger().SetLevel(output, level)
+}
+
+// GetLevel gets log level for different output.
+func GetLevel(output string) Level {
+ return GetDefaultLogger().GetLevel(output)
+}
+
+// With adds user defined fields to Logger. Field support multiple values.
+func With(fields ...Field) Logger {
+ return GetDefaultLogger().With(fields...)
+}
+
+// RedirectStdLog redirects std log as log level INFO.
+// After redirection, log flag is zero, the prefix is empty.
+// The returned function may be used to recover log flag and prefix, and redirect output to
+// os.Stderr.
+func RedirectStdLog(logger Logger) (func(), error) {
+ return RedirectStdLogAt(logger, zap.InfoLevel)
+}
+
+// RedirectStdLogAt redirects std log with a specific level.
+// After redirection, log flag is zero, the prefix is empty.
+// The returned function may be used to recover log flag and prefix, and redirect output to
+// os.Stderr.
+func RedirectStdLogAt(logger Logger, level zapcore.Level) (func(), error) {
+ if l, ok := logger.(*zapLog); ok {
+ return zap.RedirectStdLogAt(l.logger, level)
+ }
+ if l, ok := logger.(*ZapLogWrapper); ok {
+ return zap.RedirectStdLogAt(l.l.logger, level)
+ }
+ return nil, fmt.Errorf("log: only supports redirecting std logs to zap logger")
+}
+
+// Debug logs to DEBUG log. Arguments are handled in the manner of fmt.Print.
+func Debug(args ...interface{}) {
+ GetDefaultLogger().Debug(args...)
+}
+
+// Debugf logs to DEBUG log. Arguments are handled in the manner of fmt.Printf.
+func Debugf(format string, args ...interface{}) {
+ GetDefaultLogger().Debugf(format, args...)
+}
+
+// Info logs to INFO log. Arguments are handled in the manner of fmt.Print.
+func Info(args ...interface{}) {
+ GetDefaultLogger().Info(args...)
+}
+
+// Infof logs to INFO log. Arguments are handled in the manner of fmt.Printf.
+func Infof(format string, args ...interface{}) {
+ GetDefaultLogger().Infof(format, args...)
+}
+
+// Warn logs to WARNING log. Arguments are handled in the manner of fmt.Print.
+func Warn(args ...interface{}) {
+ GetDefaultLogger().Warn(args...)
+}
+
+// Warnf logs to WARNING log. Arguments are handled in the manner of fmt.Printf.
+func Warnf(format string, args ...interface{}) {
+ GetDefaultLogger().Warnf(format, args...)
+}
+
+// Error logs to ERROR log. Arguments are handled in the manner of fmt.Print.
+func Error(args ...interface{}) {
+ GetDefaultLogger().Error(args...)
+}
+
+// Errorf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
+func Errorf(format string, args ...interface{}) {
+ GetDefaultLogger().Errorf(format, args...)
+}
+
+// Fatal logs to ERROR log. Arguments are handled in the manner of fmt.Print.
+// All Fatal logs will exit by calling os.Exit(1).
+// Implementations may also call os.Exit() with a non-zero exit code.
+func Fatal(args ...interface{}) {
+ GetDefaultLogger().Fatal(args...)
+}
+
+// Fatalf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
+func Fatalf(format string, args ...interface{}) {
+ GetDefaultLogger().Fatalf(format, args...)
+}
diff --git a/log/logger.go b/log/logger.go
new file mode 100644
index 0000000..c1ffa78
--- /dev/null
+++ b/log/logger.go
@@ -0,0 +1,120 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package log
+
+import (
+ "io"
+)
+
+// Level is the log level.
+type Level int
+
+// Enums log level constants.
+const (
+ LevelNil Level = iota
+ LevelTrace
+ LevelDebug
+ LevelInfo
+ LevelWarn
+ LevelError
+ LevelFatal
+)
+
+// String turns the LogLevel to string.
+func (lv *Level) String() string {
+ return LevelStrings[*lv]
+}
+
+// LevelStrings is the map from log level to its string representation.
+var LevelStrings = map[Level]string{
+ LevelTrace: "trace",
+ LevelDebug: "debug",
+ LevelInfo: "info",
+ LevelWarn: "warn",
+ LevelError: "error",
+ LevelFatal: "fatal",
+}
+
+// LevelNames is the map from string to log level.
+var LevelNames = map[string]Level{
+ "trace": LevelTrace,
+ "debug": LevelDebug,
+ "info": LevelInfo,
+ "warn": LevelWarn,
+ "error": LevelError,
+ "fatal": LevelFatal,
+}
+
+// LoggerOptions is the log options.
+type LoggerOptions struct {
+ LogLevel Level
+ Pattern string
+ Writer io.Writer
+}
+
+// LoggerOption modifies the LoggerOptions.
+type LoggerOption func(*LoggerOptions)
+
+// Field is the user defined log field.
+type Field struct {
+ Key string
+ Value interface{}
+}
+
+// Logger is the underlying logging work for server.
+type Logger interface {
+ // Trace logs to TRACE log. Arguments are handled in the manner of fmt.Print.
+ Trace(args ...interface{})
+ // Tracef logs to TRACE log. Arguments are handled in the manner of fmt.Printf.
+ Tracef(format string, args ...interface{})
+ // Debug logs to DEBUG log. Arguments are handled in the manner of fmt.Print.
+ Debug(args ...interface{})
+ // Debugf logs to DEBUG log. Arguments are handled in the manner of fmt.Printf.
+ Debugf(format string, args ...interface{})
+ // Info logs to INFO log. Arguments are handled in the manner of fmt.Print.
+ Info(args ...interface{})
+ // Infof logs to INFO log. Arguments are handled in the manner of fmt.Printf.
+ Infof(format string, args ...interface{})
+ // Warn logs to WARNING log. Arguments are handled in the manner of fmt.Print.
+ Warn(args ...interface{})
+ // Warnf logs to WARNING log. Arguments are handled in the manner of fmt.Printf.
+ Warnf(format string, args ...interface{})
+ // Error logs to ERROR log. Arguments are handled in the manner of fmt.Print.
+ Error(args ...interface{})
+ // Errorf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
+ Errorf(format string, args ...interface{})
+ // Fatal logs to ERROR log. Arguments are handled in the manner of fmt.Print.
+ // All Fatal logs will exit by calling os.Exit(1).
+ // Implementations may also call os.Exit() with a non-zero exit code.
+ Fatal(args ...interface{})
+ // Fatalf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
+ Fatalf(format string, args ...interface{})
+
+ // Sync calls the underlying Core's Sync method, flushing any buffered log entries.
+ // Applications should take care to call Sync before exiting.
+ Sync() error
+
+ // SetLevel set the output log level.
+ SetLevel(output string, level Level)
+ // GetLevel get the output log level.
+ GetLevel(output string) Level
+ // WithFields set some user defined data to logs, such as uid, imei, etc.
+ // Fields must be paired.
+ // Deprecated: use With instead.
+ WithFields(fields ...string) Logger
+ // With add user defined fields to Logger. Fields support multiple values.
+ With(fields ...Field) Logger
+}
diff --git a/log/logger_factory.go b/log/logger_factory.go
new file mode 100644
index 0000000..b53e19f
--- /dev/null
+++ b/log/logger_factory.go
@@ -0,0 +1,155 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package log
+
+import (
+ "errors"
+ "fmt"
+ "sync"
+
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+func init() {
+ RegisterWriter(OutputConsole, DefaultConsoleWriterFactory)
+ RegisterWriter(OutputFile, DefaultFileWriterFactory)
+ Register(defaultLoggerName, NewZapLog(defaultConfig))
+ plugin.Register(defaultLoggerName, DefaultLogFactory)
+}
+
+const (
+ pluginType = "log"
+ defaultLoggerName = "default"
+)
+
+var (
+ // DefaultLogger the default Logger. The initial output is console. When frame start, it is
+ // over write by configuration.
+ DefaultLogger Logger
+ // DefaultLogFactory is the default log loader. Users may replace it with their own
+ // implementation.
+ DefaultLogFactory = &Factory{}
+
+ mu sync.RWMutex
+ loggers = make(map[string]Logger)
+)
+
+// Register registers Logger. It supports multiple Logger implementation.
+func Register(name string, logger Logger) {
+ mu.Lock()
+ defer mu.Unlock()
+ if logger == nil {
+ panic("log: Register logger is nil")
+ }
+ if _, dup := loggers[name]; dup && name != defaultLoggerName {
+ panic("log: Register called twice for logger name " + name)
+ }
+ loggers[name] = logger
+ if name == defaultLoggerName {
+ DefaultLogger = logger
+ }
+}
+
+// GetDefaultLogger gets the default Logger.
+// To configure it, set key in configuration file to default.
+// The console output is the default value.
+func GetDefaultLogger() Logger {
+ mu.RLock()
+ l := DefaultLogger
+ mu.RUnlock()
+ return l
+}
+
+// SetLogger sets the default Logger.
+func SetLogger(logger Logger) {
+ mu.Lock()
+ DefaultLogger = logger
+ mu.Unlock()
+}
+
+// Get returns the Logger implementation by log name.
+// log.Debug use DefaultLogger to print logs. You may also use log.Get("name").Debug.
+func Get(name string) Logger {
+ mu.RLock()
+ l := loggers[name]
+ mu.RUnlock()
+ return l
+}
+
+// Decoder decodes the log.
+type Decoder struct {
+ OutputConfig *OutputConfig
+ Core zapcore.Core
+ ZapLevel zap.AtomicLevel
+}
+
+// Decode decodes writer configuration, copy one.
+func (d *Decoder) Decode(cfg interface{}) error {
+ output, ok := cfg.(**OutputConfig)
+ if !ok {
+ return fmt.Errorf("decoder config type:%T invalid, not **OutputConfig", cfg)
+ }
+ *output = d.OutputConfig
+ return nil
+}
+
+// Factory is the log plugin factory.
+// When server start, the configuration is feed to Factory to generate a log instance.
+type Factory struct {
+}
+
+// Type returns the log plugin type.
+func (f *Factory) Type() string {
+ return pluginType
+}
+
+// Setup starts, load and register logs.
+func (f *Factory) Setup(name string, dec plugin.Decoder) error {
+ if dec == nil {
+ return errors.New("log config decoder empty")
+ }
+ cfg, callerSkip, err := f.setupConfig(dec)
+ if err != nil {
+ return err
+ }
+ logger := NewZapLogWithCallerSkip(cfg, callerSkip)
+ if logger == nil {
+ return errors.New("new zap logger fail")
+ }
+ Register(name, logger)
+ return nil
+}
+
+func (f *Factory) setupConfig(configDec plugin.Decoder) (Config, int, error) {
+ cfg := Config{}
+ if err := configDec.Decode(&cfg); err != nil {
+ return nil, 0, err
+ }
+ if len(cfg) == 0 {
+ return nil, 0, errors.New("log config output empty")
+ }
+
+ // If caller skip is not configured, use 2 as default.
+ callerSkip := 2
+ for i := 0; i < len(cfg); i++ {
+ if cfg[i].CallerSkip != 0 {
+ callerSkip = cfg[i].CallerSkip
+ }
+ }
+ return cfg, callerSkip, nil
+}
diff --git a/log/rollwriter/async_roll_writer.go b/log/rollwriter/async_roll_writer.go
new file mode 100644
index 0000000..e39e7ec
--- /dev/null
+++ b/log/rollwriter/async_roll_writer.go
@@ -0,0 +1,133 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package rollwriter
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "time"
+
+ "github.com/hashicorp/go-multierror"
+)
+
+// AsyncRollWriter is the asynchronous rolling log writer which implements zapcore.WriteSyncer.
+type AsyncRollWriter struct {
+ logger io.WriteCloser
+ opts *AsyncOptions
+
+ logQueue chan []byte
+ sync chan struct{}
+ syncErr chan error
+ close chan struct{}
+ closeErr chan error
+}
+
+// NewAsyncRollWriter create a new AsyncRollWriter.
+func NewAsyncRollWriter(logger io.WriteCloser, opt ...AsyncOption) *AsyncRollWriter {
+ opts := &AsyncOptions{
+ LogQueueSize: 10000, // default queue size as 10000
+ WriteLogSize: 4 * 1024, // default write log size as 4K
+ WriteLogInterval: 100, // default sync interval as 100ms
+ DropLog: false, // default do not drop logs
+ }
+
+ for _, o := range opt {
+ o(opts)
+ }
+
+ w := &AsyncRollWriter{}
+ w.logger = logger
+ w.opts = opts
+ w.logQueue = make(chan []byte, opts.LogQueueSize)
+ w.sync = make(chan struct{})
+ w.syncErr = make(chan error)
+ w.close = make(chan struct{})
+ w.closeErr = make(chan error)
+
+ // start a new goroutine write batch logs.
+ go w.batchWriteLog()
+ return w
+}
+
+// Write writes logs. It implements io.Writer.
+func (w *AsyncRollWriter) Write(data []byte) (int, error) {
+ log := make([]byte, len(data))
+ copy(log, data)
+ if w.opts.DropLog {
+ select {
+ case w.logQueue <- log:
+ default:
+ return 0, errors.New("log queue is full")
+ }
+ } else {
+ w.logQueue <- log
+ }
+ return len(data), nil
+}
+
+// Sync syncs logs. It implements zapcore.WriteSyncer.
+func (w *AsyncRollWriter) Sync() error {
+ w.sync <- struct{}{}
+ return <-w.syncErr
+}
+
+// Close closes current log file. It implements io.Closer.
+func (w *AsyncRollWriter) Close() error {
+ err := w.Sync()
+ close(w.close)
+ return multierror.Append(err, <-w.closeErr).ErrorOrNil()
+}
+
+// batchWriteLog asynchronously writes logs in batches.
+func (w *AsyncRollWriter) batchWriteLog() {
+ buffer := bytes.NewBuffer(make([]byte, 0, w.opts.WriteLogSize*2))
+ ticker := time.NewTicker(time.Millisecond * time.Duration(w.opts.WriteLogInterval))
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-ticker.C:
+ if buffer.Len() > 0 {
+ _, _ = w.logger.Write(buffer.Bytes())
+ buffer.Reset()
+ }
+ case data := <-w.logQueue:
+ buffer.Write(data)
+ if buffer.Len() >= w.opts.WriteLogSize {
+ _, _ = w.logger.Write(buffer.Bytes())
+ buffer.Reset()
+ }
+ case <-w.sync:
+ var err error
+ if buffer.Len() > 0 {
+ _, e := w.logger.Write(buffer.Bytes())
+ err = multierror.Append(err, e).ErrorOrNil()
+ buffer.Reset()
+ }
+ size := len(w.logQueue)
+ for i := 0; i < size; i++ {
+ v := <-w.logQueue
+ _, e := w.logger.Write(v)
+ err = multierror.Append(err, e).ErrorOrNil()
+ }
+ w.syncErr <- err
+ case <-w.close:
+ w.closeErr <- w.logger.Close()
+ return
+ }
+ }
+}
diff --git a/log/rollwriter/async_roll_writer_options.go b/log/rollwriter/async_roll_writer_options.go
new file mode 100644
index 0000000..c3a43f4
--- /dev/null
+++ b/log/rollwriter/async_roll_writer_options.go
@@ -0,0 +1,62 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package rollwriter
+
+// AsyncOptions is the call options of AsyncRollWriter.
+type AsyncOptions struct {
+ // LogQueueSize is the queue size of asynchronous log.
+ LogQueueSize int
+
+ // WriteLogSize is the threshold to write async log.
+ WriteLogSize int
+
+ // WriteLogInterval is the time interval to write async log.
+ WriteLogInterval int
+
+ // DropLog determines whether to discard logs when log queue is full.
+ DropLog bool
+}
+
+// AsyncOption modifies the AsyncOptions.
+type AsyncOption func(*AsyncOptions)
+
+// WithLogQueueSize returns an AsyncOption which sets log queue size.
+func WithLogQueueSize(n int) AsyncOption {
+ return func(o *AsyncOptions) {
+ o.LogQueueSize = n
+ }
+}
+
+// WithWriteLogSize returns an AsyncOption which sets log size(Byte) threshold.
+func WithWriteLogSize(n int) AsyncOption {
+ return func(o *AsyncOptions) {
+ o.WriteLogSize = n
+ }
+}
+
+// WithWriteLogInterval returns an AsyncOption which sets log interval(ms) threshold(ms).
+func WithWriteLogInterval(n int) AsyncOption {
+ return func(o *AsyncOptions) {
+ o.WriteLogInterval = n
+ }
+}
+
+// WithDropLog returns an AsyncOption which set whether to drop logs on log queue full.
+func WithDropLog(b bool) AsyncOption {
+ return func(o *AsyncOptions) {
+ o.DropLog = b
+ }
+}
diff --git a/log/rollwriter/roll_writer.go b/log/rollwriter/roll_writer.go
new file mode 100644
index 0000000..2a5f75d
--- /dev/null
+++ b/log/rollwriter/roll_writer.go
@@ -0,0 +1,446 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// Package rollwriter provides a high performance rolling file log.
+// It can coordinate with any logs which depends on io.Writer, such as golang standard log.
+// Main features:
+// 1. support rolling logs by file size.
+// 2. support rolling logs by datetime.
+// 3. support scavenging expired or useless logs.
+// 4. support compressing logs.
+package rollwriter
+
+import (
+ "compress/gzip"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/lestrrat-go/strftime"
+)
+
+const (
+ backupTimeFormat = "bk-20060102-150405.00000"
+ compressSuffix = ".gz"
+)
+
+// ensure we always implement io.WriteCloser.
+var _ io.WriteCloser = (*RollWriter)(nil)
+
+// RollWriter is a file log writer which support rolling by size or datetime.
+// It implements io.WriteCloser.
+type RollWriter struct {
+ filePath string
+ opts *Options
+
+ pattern *strftime.Strftime
+ currDir string
+ currPath string
+ currSize int64
+ currFile atomic.Value
+ openTime int64
+
+ mu sync.Mutex
+ notifyOnce sync.Once
+ notifyCh chan bool
+ closeOnce sync.Once
+ closeCh chan *os.File
+}
+
+// NewRollWriter creates a new RollWriter.
+func NewRollWriter(filePath string, opt ...Option) (*RollWriter, error) {
+ opts := &Options{
+ MaxSize: 0, // default no rolling by file size
+ MaxAge: 0, // default no scavenging on expired logs
+ MaxBackups: 0, // default no scavenging on redundant logs
+ Compress: false, // default no compressing
+ }
+
+ // opt has the highest priority and should overwrite the original one.
+ for _, o := range opt {
+ o(opts)
+ }
+
+ if filePath == "" {
+ return nil, errors.New("invalid file path")
+ }
+
+ pattern, err := strftime.New(filePath + opts.TimeFormat)
+ if err != nil {
+ return nil, errors.New("invalid time pattern")
+ }
+
+ w := &RollWriter{
+ filePath: filePath,
+ opts: opts,
+ pattern: pattern,
+ currDir: filepath.Dir(filePath),
+ }
+
+ if err := os.MkdirAll(w.currDir, 0755); err != nil {
+ return nil, err
+ }
+
+ return w, nil
+}
+
+// Write writes logs. It implements io.Writer.
+func (w *RollWriter) Write(v []byte) (n int, err error) {
+ // reopen file every 10 seconds.
+ if w.getCurrFile() == nil || time.Now().Unix()-atomic.LoadInt64(&w.openTime) > 10 {
+ w.mu.Lock()
+ w.reopenFile()
+ w.mu.Unlock()
+ }
+
+ // return when failed to open the file.
+ if w.getCurrFile() == nil {
+ return 0, errors.New("open file fail")
+ }
+
+ // write logs to file.
+ n, err = w.getCurrFile().Write(v)
+ atomic.AddInt64(&w.currSize, int64(n))
+
+ // rolling on full
+ if w.opts.MaxSize > 0 && atomic.LoadInt64(&w.currSize) >= w.opts.MaxSize {
+ w.mu.Lock()
+ w.backupFile()
+ w.mu.Unlock()
+ }
+ return n, err
+}
+
+// Close closes the current log file. It implements io.Closer.
+func (w *RollWriter) Close() error {
+ if w.getCurrFile() == nil {
+ return nil
+ }
+ err := w.getCurrFile().Close()
+ w.setCurrFile(nil)
+
+ if w.notifyCh != nil {
+ close(w.notifyCh)
+ w.notifyCh = nil
+ }
+
+ if w.closeCh != nil {
+ close(w.closeCh)
+ w.closeCh = nil
+ }
+
+ return err
+}
+
+// getCurrFile returns the current log file.
+func (w *RollWriter) getCurrFile() *os.File {
+ if file, ok := w.currFile.Load().(*os.File); ok {
+ return file
+ }
+ return nil
+}
+
+// setCurrFile sets the current log file.
+func (w *RollWriter) setCurrFile(file *os.File) {
+ w.currFile.Store(file)
+}
+
+// reopenFile reopen the file regularly. It notifies the scavenger if file path has changed.
+func (w *RollWriter) reopenFile() {
+ if w.getCurrFile() == nil || time.Now().Unix()-atomic.LoadInt64(&w.openTime) > 10 {
+ atomic.StoreInt64(&w.openTime, time.Now().Unix())
+ currPath := w.pattern.FormatString(time.Now())
+ if w.currPath != currPath {
+ w.currPath = currPath
+ w.notify()
+ }
+ _ = w.doReopenFile(w.currPath)
+ }
+}
+
+// doReopenFile reopen the file.
+func (w *RollWriter) doReopenFile(path string) error {
+ atomic.StoreInt64(&w.openTime, time.Now().Unix())
+ lastFile := w.getCurrFile()
+ of, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
+ if err == nil {
+ w.setCurrFile(of)
+ if lastFile != nil {
+ // delay closing until not used.
+ w.delayCloseFile(lastFile)
+ }
+ st, _ := os.Stat(path)
+ if st != nil {
+ atomic.StoreInt64(&w.currSize, st.Size())
+ }
+ }
+ return err
+}
+
+// backupFile backs this file up and reopen a new one if file size is too large.
+func (w *RollWriter) backupFile() {
+ if w.opts.MaxSize > 0 && atomic.LoadInt64(&w.currSize) >= w.opts.MaxSize {
+ atomic.StoreInt64(&w.currSize, 0)
+
+ // rename the old file.
+ newName := w.currPath + "." + time.Now().Format(backupTimeFormat)
+ if _, e := os.Stat(w.currPath); !os.IsNotExist(e) {
+ _ = os.Rename(w.currPath, newName)
+ }
+
+ // reopen a new one.
+ _ = w.doReopenFile(w.currPath)
+ w.notify()
+ }
+}
+
+// notify runs scavengers.
+func (w *RollWriter) notify() {
+ w.notifyOnce.Do(func() {
+ w.notifyCh = make(chan bool, 1)
+ go w.runCleanFiles()
+ })
+ select {
+ case w.notifyCh <- true:
+ default:
+ }
+}
+
+// runCleanFiles cleans redundant or expired (compressed) logs in a new goroutine.
+func (w *RollWriter) runCleanFiles() {
+ for range w.notifyCh {
+ if w.opts.MaxBackups == 0 && w.opts.MaxAge == 0 && !w.opts.Compress {
+ continue
+ }
+ w.cleanFiles()
+ }
+}
+
+// delayCloseFile delay closing file
+func (w *RollWriter) delayCloseFile(file *os.File) {
+ w.closeOnce.Do(func() {
+ w.closeCh = make(chan *os.File, 100)
+ go w.runCloseFiles()
+ })
+ w.closeCh <- file
+}
+
+// runCloseFiles delay closing file in a new goroutine.
+func (w *RollWriter) runCloseFiles() {
+ for f := range w.closeCh {
+ // delay 20ms
+ time.Sleep(20 * time.Millisecond)
+ f.Close()
+ }
+}
+
+// cleanFiles cleans redundant or expired (compressed) logs.
+func (w *RollWriter) cleanFiles() {
+ // get the file list of current log.
+ files, err := w.getOldLogFiles()
+ if err != nil || len(files) == 0 {
+ return
+ }
+
+ // find the oldest files to scavenge.
+ var compress, remove []logInfo
+ files = filterByMaxBackups(files, &remove, w.opts.MaxBackups)
+
+ // find the expired files by last modified time.
+ files = filterByMaxAge(files, &remove, w.opts.MaxAge)
+
+ // find files to compress by file extension .gz.
+ filterByCompressExt(files, &compress, w.opts.Compress)
+
+ // delete expired or redundant files.
+ w.removeFiles(remove)
+
+ // compress log files.
+ w.compressFiles(compress)
+}
+
+// getOldLogFiles returns the log file list ordered by modified time.
+func (w *RollWriter) getOldLogFiles() ([]logInfo, error) {
+ files, err := ioutil.ReadDir(w.currDir)
+ if err != nil {
+ return nil, fmt.Errorf("can't read log file directory: %s", err)
+ }
+ logFiles := []logInfo{}
+ filename := filepath.Base(w.filePath)
+ for _, f := range files {
+ if f.IsDir() {
+ continue
+ }
+
+ if modTime, err := w.matchLogFile(f.Name(), filename); err == nil {
+ logFiles = append(logFiles, logInfo{modTime, f})
+ }
+ }
+ sort.Sort(byFormatTime(logFiles))
+ return logFiles, nil
+}
+
+// matchLogFile checks whether current log file matches all relative log files, if matched, returns
+// the modified time.
+func (w *RollWriter) matchLogFile(filename, filePrefix string) (time.Time, error) {
+ // exclude current log file.
+ // a.log
+ // a.log.20200712
+ if filepath.Base(w.currPath) == filename {
+ return time.Time{}, errors.New("ignore current logfile")
+ }
+
+ // match all log files with current log file.
+ // a.log -> a.log.20200712-1232/a.log.20200712-1232.gz
+ // a.log.20200712 -> a.log.20200712.20200712-1232/a.log.20200712.20200712-1232.gz
+ if !strings.HasPrefix(filename, filePrefix) {
+ return time.Time{}, errors.New("mismatched prefix")
+ }
+
+ if st, _ := os.Stat(filepath.Join(w.currDir, filename)); st != nil {
+ return st.ModTime(), nil
+ }
+ return time.Time{}, errors.New("file stat fail")
+}
+
+// removeFiles deletes expired or redundant log files.
+func (w *RollWriter) removeFiles(remove []logInfo) {
+ // clean expired or redundant files.
+ for _, f := range remove {
+ os.Remove(filepath.Join(w.currDir, f.Name()))
+ }
+}
+
+// compressFiles compresses demanded log files.
+func (w *RollWriter) compressFiles(compress []logInfo) {
+ // compress log files.
+ for _, f := range compress {
+ fn := filepath.Join(w.currDir, f.Name())
+ _ = compressFile(fn, fn+compressSuffix)
+ }
+}
+
+// filterByMaxBackups filters redundant files that exceeded the limit.
+func filterByMaxBackups(files []logInfo, remove *[]logInfo, maxBackups int) []logInfo {
+ if maxBackups == 0 || len(files) < maxBackups {
+ return files
+ }
+ var remaining []logInfo
+ preserved := make(map[string]bool)
+ for _, f := range files {
+ fn := strings.TrimSuffix(f.Name(), compressSuffix)
+ preserved[fn] = true
+
+ if len(preserved) > maxBackups {
+ *remove = append(*remove, f)
+ } else {
+ remaining = append(remaining, f)
+ }
+ }
+ return remaining
+}
+
+// filterByMaxAge filters expired files.
+func filterByMaxAge(files []logInfo, remove *[]logInfo, maxAge int) []logInfo {
+ if maxAge <= 0 {
+ return files
+ }
+ var remaining []logInfo
+ diff := time.Duration(int64(24*time.Hour) * int64(maxAge))
+ cutoff := time.Now().Add(-1 * diff)
+ for _, f := range files {
+ if f.timestamp.Before(cutoff) {
+ *remove = append(*remove, f)
+ } else {
+ remaining = append(remaining, f)
+ }
+ }
+ return remaining
+}
+
+// filterByCompressExt filters all compressed files.
+func filterByCompressExt(files []logInfo, compress *[]logInfo, needCompress bool) {
+ if !needCompress {
+ return
+ }
+ for _, f := range files {
+ if !strings.HasSuffix(f.Name(), compressSuffix) {
+ *compress = append(*compress, f)
+ }
+ }
+}
+
+// compressFile compresses file src to dst, and removes src on success.
+func compressFile(src, dst string) (err error) {
+ f, err := os.Open(src)
+ if err != nil {
+ return fmt.Errorf("failed to open file: %v", err)
+ }
+ defer f.Close()
+
+ gzf, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
+ if err != nil {
+ return fmt.Errorf("failed to open compressed file: %v", err)
+ }
+ defer gzf.Close()
+
+ gz := gzip.NewWriter(gzf)
+ defer func() {
+ gz.Close()
+ if err != nil {
+ os.Remove(dst)
+ err = fmt.Errorf("failed to compress file: %v", err)
+ } else {
+ os.Remove(src)
+ }
+ }()
+
+ if _, err := io.Copy(gz, f); err != nil {
+ return err
+ }
+ return nil
+}
+
+// logInfo is an assistant struct which is used to return file name and last modified time.
+type logInfo struct {
+ timestamp time.Time
+ os.FileInfo
+}
+
+// byFormatTime sorts by time descending order.
+type byFormatTime []logInfo
+
+// Less checks whether the time of b[j] is early than the time of b[i].
+func (b byFormatTime) Less(i, j int) bool {
+ return b[i].timestamp.After(b[j].timestamp)
+}
+
+// Swap swaps b[i] and b[j].
+func (b byFormatTime) Swap(i, j int) {
+ b[i], b[j] = b[j], b[i]
+}
+
+// Len returns the length of list b.
+func (b byFormatTime) Len() int {
+ return len(b)
+}
diff --git a/log/rollwriter/roll_writer_options.go b/log/rollwriter/roll_writer_options.go
new file mode 100644
index 0000000..8830fe7
--- /dev/null
+++ b/log/rollwriter/roll_writer_options.go
@@ -0,0 +1,72 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package rollwriter
+
+// Options is the RollWriter call options.
+type Options struct {
+ // MaxSize is max size by byte of the log file.
+ MaxSize int64
+
+ // MaxBackups is the max number of log files.
+ MaxBackups int
+
+ // MaxAge is the max expire time by day of log files.
+ MaxAge int
+
+ // whether the log file should be compressed.
+ Compress bool
+
+ // TimeFormat is the time format to split log file by time.
+ TimeFormat string
+}
+
+// Option modifies the Options.
+type Option func(*Options)
+
+// WithMaxSize returns an Option which sets the max size(MB) of log files.
+func WithMaxSize(n int) Option {
+ return func(o *Options) {
+ o.MaxSize = int64(n) * 1024 * 1024
+ }
+}
+
+// WithMaxAge returns an Option which sets the max expire time(Day) of log files.
+func WithMaxAge(n int) Option {
+ return func(o *Options) {
+ o.MaxAge = n
+ }
+}
+
+// WithMaxBackups returns an Option which sets the max number of backup log files.
+func WithMaxBackups(n int) Option {
+ return func(o *Options) {
+ o.MaxBackups = n
+ }
+}
+
+// WithCompress returns an Option which sets whether log files should be compressed.
+func WithCompress(b bool) Option {
+ return func(o *Options) {
+ o.Compress = b
+ }
+}
+
+// WithRotationTime returns an Option which sets the time format(%Y%m%d) to roll logs.
+func WithRotationTime(s string) Option {
+ return func(o *Options) {
+ o.TimeFormat = s
+ }
+}
diff --git a/log/writer_factory.go b/log/writer_factory.go
new file mode 100644
index 0000000..b2bd073
--- /dev/null
+++ b/log/writer_factory.go
@@ -0,0 +1,116 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package log
+
+import (
+ "errors"
+ "path/filepath"
+
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+)
+
+var (
+ // DefaultConsoleWriterFactory is the default console output implementation.
+ DefaultConsoleWriterFactory = &ConsoleWriterFactory{}
+ // DefaultFileWriterFactory is the default file output implementation.
+ DefaultFileWriterFactory = &FileWriterFactory{}
+
+ writers = make(map[string]plugin.Plugin)
+)
+
+// RegisterWriter registers log output writer. Writer may have multiple implementations.
+func RegisterWriter(name string, writer plugin.Plugin) {
+ writers[name] = writer
+}
+
+// GetWriter gets log output writer, returns nil if not exist.
+func GetWriter(name string) plugin.Plugin {
+ return writers[name]
+}
+
+// ConsoleWriterFactory is the console writer instance.
+type ConsoleWriterFactory struct {
+}
+
+// Setup starts, loads and registers console output writer.
+func (f *ConsoleWriterFactory) Setup(name string, dec plugin.Decoder) error {
+ if dec == nil {
+ return errors.New("console writer decoder empty")
+ }
+ decoder, ok := dec.(*Decoder)
+ if !ok {
+ return errors.New("console writer log decoder type invalid")
+ }
+ cfg := &OutputConfig{}
+ if err := decoder.Decode(&cfg); err != nil {
+ return err
+ }
+ decoder.Core, decoder.ZapLevel = newConsoleCore(cfg)
+ return nil
+}
+
+// Type returns log file type.
+func (f *FileWriterFactory) Type() string {
+ return pluginType
+}
+
+// FileWriterFactory is the file writer instance Factory.
+type FileWriterFactory struct {
+}
+
+// Type returns log file type.
+func (f *ConsoleWriterFactory) Type() string {
+ return pluginType
+}
+
+// Setup starts, loads and register file output writer.
+func (f *FileWriterFactory) Setup(name string, dec plugin.Decoder) error {
+ if dec == nil {
+ return errors.New("file writer decoder empty")
+ }
+ decoder, ok := dec.(*Decoder)
+ if !ok {
+ return errors.New("file writer log decoder type invalid")
+ }
+ if err := f.setupConfig(decoder); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (f *FileWriterFactory) setupConfig(decoder *Decoder) error {
+ cfg := &OutputConfig{}
+ if err := decoder.Decode(&cfg); err != nil {
+ return err
+ }
+ if cfg.WriteConfig.LogPath != "" {
+ cfg.WriteConfig.Filename = filepath.Join(cfg.WriteConfig.LogPath, cfg.WriteConfig.Filename)
+ }
+ if cfg.WriteConfig.RollType == "" {
+ cfg.WriteConfig.RollType = RollBySize
+ }
+ if cfg.WriteConfig.WriteMode == 0 {
+ // Use WriteFast as default mod.
+ // It has better performance, discards logs on full and avoid blocking service.
+ cfg.WriteConfig.WriteMode = WriteFast
+ }
+ core, level, err := newFileCore(cfg)
+ if err != nil {
+ return err
+ }
+ decoder.Core, decoder.ZapLevel = core, level
+ return nil
+}
diff --git a/log/zaplogger.go b/log/zaplogger.go
new file mode 100644
index 0000000..c16f20f
--- /dev/null
+++ b/log/zaplogger.go
@@ -0,0 +1,502 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package log
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/log/rollwriter"
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+var defaultConfig = []OutputConfig{
+ {
+ Writer: "console",
+ Level: "debug",
+ Formatter: "console",
+ },
+}
+
+// Some ZapCore constants.
+const (
+ ConsoleZapCore = "console"
+ FileZapCore = "file"
+)
+
+// Levels is the map from string to zapcore.Level.
+var Levels = map[string]zapcore.Level{
+ "": zapcore.DebugLevel,
+ "debug": zapcore.DebugLevel,
+ "info": zapcore.InfoLevel,
+ "warn": zapcore.WarnLevel,
+ "error": zapcore.ErrorLevel,
+ "fatal": zapcore.FatalLevel,
+}
+
+var levelToZapLevel = map[Level]zapcore.Level{
+ LevelTrace: zapcore.DebugLevel,
+ LevelDebug: zapcore.DebugLevel,
+ LevelInfo: zapcore.InfoLevel,
+ LevelWarn: zapcore.WarnLevel,
+ LevelError: zapcore.ErrorLevel,
+ LevelFatal: zapcore.FatalLevel,
+}
+
+var zapLevelToLevel = map[zapcore.Level]Level{
+ zapcore.DebugLevel: LevelDebug,
+ zapcore.InfoLevel: LevelInfo,
+ zapcore.WarnLevel: LevelWarn,
+ zapcore.ErrorLevel: LevelError,
+ zapcore.FatalLevel: LevelFatal,
+}
+
+// NewZapLog creates a workflow default Logger from zap whose caller skip is set to 2.
+func NewZapLog(c Config) Logger {
+ return NewZapLogWithCallerSkip(c, 2)
+}
+
+// NewZapLogWithCallerSkip creates a workflow default Logger from zap.
+func NewZapLogWithCallerSkip(c Config, callerSkip int) Logger {
+ var (
+ cores []zapcore.Core
+ levels []zap.AtomicLevel
+ )
+ for _, o := range c {
+ writer := GetWriter(o.Writer)
+ if writer == nil {
+ panic("log: writer core: " + o.Writer + " no registered")
+ }
+ decoder := &Decoder{OutputConfig: &o}
+ if err := writer.Setup(o.Writer, decoder); err != nil {
+ panic("log: writer core: " + o.Writer + " setup fail: " + err.Error())
+ }
+ cores = append(cores, decoder.Core)
+ levels = append(levels, decoder.ZapLevel)
+ }
+ return &zapLog{
+ levels: levels,
+ logger: zap.New(
+ zapcore.NewTee(cores...),
+ zap.AddCallerSkip(callerSkip),
+ zap.AddCaller(),
+ ),
+ }
+}
+
+func newEncoder(c *OutputConfig) zapcore.Encoder {
+ encoderCfg := zapcore.EncoderConfig{
+ TimeKey: GetLogEncoderKey("T", c.FormatConfig.TimeKey),
+ LevelKey: GetLogEncoderKey("L", c.FormatConfig.LevelKey),
+ NameKey: GetLogEncoderKey("N", c.FormatConfig.NameKey),
+ CallerKey: GetLogEncoderKey("C", c.FormatConfig.CallerKey),
+ FunctionKey: GetLogEncoderKey(zapcore.OmitKey, c.FormatConfig.FunctionKey),
+ MessageKey: GetLogEncoderKey("M", c.FormatConfig.MessageKey),
+ StacktraceKey: GetLogEncoderKey("S", c.FormatConfig.StacktraceKey),
+ LineEnding: zapcore.DefaultLineEnding,
+ EncodeLevel: zapcore.CapitalLevelEncoder,
+ EncodeTime: NewTimeEncoder(c.FormatConfig.TimeFmt),
+ EncodeDuration: zapcore.StringDurationEncoder,
+ EncodeCaller: zapcore.ShortCallerEncoder,
+ }
+ switch c.Formatter {
+ case "console":
+ return zapcore.NewConsoleEncoder(encoderCfg)
+ case "json":
+ return zapcore.NewJSONEncoder(encoderCfg)
+ default:
+ return zapcore.NewConsoleEncoder(encoderCfg)
+ }
+}
+
+// GetLogEncoderKey gets user defined log output name, uses defKey if empty.
+func GetLogEncoderKey(defKey, key string) string {
+ if key == "" {
+ return defKey
+ }
+ return key
+}
+
+func newConsoleCore(c *OutputConfig) (zapcore.Core, zap.AtomicLevel) {
+ lvl := zap.NewAtomicLevelAt(Levels[c.Level])
+ return zapcore.NewCore(
+ newEncoder(c),
+ zapcore.Lock(os.Stdout),
+ lvl), lvl
+}
+
+func newFileCore(c *OutputConfig) (zapcore.Core, zap.AtomicLevel, error) {
+ opts := []rollwriter.Option{
+ rollwriter.WithMaxAge(c.WriteConfig.MaxAge),
+ rollwriter.WithMaxBackups(c.WriteConfig.MaxBackups),
+ rollwriter.WithCompress(c.WriteConfig.Compress),
+ rollwriter.WithMaxSize(c.WriteConfig.MaxSize),
+ }
+ // roll by time.
+ if c.WriteConfig.RollType != RollBySize {
+ opts = append(opts, rollwriter.WithRotationTime(c.WriteConfig.TimeUnit.Format()))
+ }
+ writer, err := rollwriter.NewRollWriter(c.WriteConfig.Filename, opts...)
+ if err != nil {
+ return nil, zap.AtomicLevel{}, err
+ }
+
+ // write mod.
+ var ws zapcore.WriteSyncer
+ if c.WriteConfig.WriteMode == WriteSync {
+ ws = zapcore.AddSync(writer)
+ } else {
+ dropLog := c.WriteConfig.WriteMode == WriteFast
+ ws = rollwriter.NewAsyncRollWriter(writer,
+ rollwriter.WithDropLog(dropLog),
+ )
+ }
+
+ // log level.
+ lvl := zap.NewAtomicLevelAt(Levels[c.Level])
+ return zapcore.NewCore(
+ newEncoder(c),
+ ws, lvl,
+ ), lvl, nil
+}
+
+// NewTimeEncoder creates a time format encoder.
+func NewTimeEncoder(format string) zapcore.TimeEncoder {
+ switch format {
+ case "":
+ return func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
+ enc.AppendByteString(DefaultTimeFormat(t))
+ }
+ case "seconds":
+ return zapcore.EpochTimeEncoder
+ case "milliseconds":
+ return zapcore.EpochMillisTimeEncoder
+ case "nanoseconds":
+ return zapcore.EpochNanosTimeEncoder
+ default:
+ return func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
+ enc.AppendString(CustomTimeFormat(t, format))
+ }
+ }
+}
+
+// CustomTimeFormat customize time format.
+func CustomTimeFormat(t time.Time, format string) string {
+ return t.Format(format)
+}
+
+// DefaultTimeFormat returns the default time format.
+func DefaultTimeFormat(t time.Time) []byte {
+ t = t.Local()
+ year, month, day := t.Date()
+ hour, minute, second := t.Clock()
+ micros := t.Nanosecond() / 1000
+
+ buf := make([]byte, 23)
+ buf[0] = byte((year/1000)%10) + '0'
+ buf[1] = byte((year/100)%10) + '0'
+ buf[2] = byte((year/10)%10) + '0'
+ buf[3] = byte(year%10) + '0'
+ buf[4] = '-'
+ buf[5] = byte((month)/10) + '0'
+ buf[6] = byte((month)%10) + '0'
+ buf[7] = '-'
+ buf[8] = byte((day)/10) + '0'
+ buf[9] = byte((day)%10) + '0'
+ buf[10] = ' '
+ buf[11] = byte((hour)/10) + '0'
+ buf[12] = byte((hour)%10) + '0'
+ buf[13] = ':'
+ buf[14] = byte((minute)/10) + '0'
+ buf[15] = byte((minute)%10) + '0'
+ buf[16] = ':'
+ buf[17] = byte((second)/10) + '0'
+ buf[18] = byte((second)%10) + '0'
+ buf[19] = '.'
+ buf[20] = byte((micros/100000)%10) + '0'
+ buf[21] = byte((micros/10000)%10) + '0'
+ buf[22] = byte((micros/1000)%10) + '0'
+ return buf
+}
+
+// ZapLogWrapper delegates zapLogger which was introduced in this
+// By ZapLogWrapper proxy, we can add a layer to the debug series function calls, so that the caller
+// information can be set correctly.
+type ZapLogWrapper struct {
+ l *zapLog
+}
+
+// GetLogger returns interval zapLog.
+func (z *ZapLogWrapper) GetLogger() Logger {
+ return z.l
+}
+
+// Trace logs to TRACE log. Arguments are handled in the manner of fmt.Print.
+func (z *ZapLogWrapper) Trace(args ...interface{}) {
+ z.l.Trace(args...)
+}
+
+// Tracef logs to TRACE log. Arguments are handled in the manner of fmt.Printf.
+func (z *ZapLogWrapper) Tracef(format string, args ...interface{}) {
+ z.l.Tracef(format, args...)
+}
+
+// Debug logs to DEBUG log. Arguments are handled in the manner of fmt.Print.
+func (z *ZapLogWrapper) Debug(args ...interface{}) {
+ z.l.Debug(args...)
+}
+
+// Debugf logs to DEBUG log. Arguments are handled in the manner of fmt.Printf.
+func (z *ZapLogWrapper) Debugf(format string, args ...interface{}) {
+ z.l.Debugf(format, args...)
+}
+
+// Info logs to INFO log. Arguments are handled in the manner of fmt.Print.
+func (z *ZapLogWrapper) Info(args ...interface{}) {
+ z.l.Info(args...)
+}
+
+// Infof logs to INFO log. Arguments are handled in the manner of fmt.Printf.
+func (z *ZapLogWrapper) Infof(format string, args ...interface{}) {
+ z.l.Infof(format, args...)
+}
+
+// Warn logs to WARNING log. Arguments are handled in the manner of fmt.Print.
+func (z *ZapLogWrapper) Warn(args ...interface{}) {
+ z.l.Warn(args...)
+}
+
+// Warnf logs to WARNING log. Arguments are handled in the manner of fmt.Printf.
+func (z *ZapLogWrapper) Warnf(format string, args ...interface{}) {
+ z.l.Warnf(format, args...)
+}
+
+// Error logs to ERROR log. Arguments are handled in the manner of fmt.Print.
+func (z *ZapLogWrapper) Error(args ...interface{}) {
+ z.l.Error(args...)
+}
+
+// Errorf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
+func (z *ZapLogWrapper) Errorf(format string, args ...interface{}) {
+ z.l.Errorf(format, args...)
+}
+
+// Fatal logs to FATAL log. Arguments are handled in the manner of fmt.Print.
+func (z *ZapLogWrapper) Fatal(args ...interface{}) {
+ z.l.Fatal(args...)
+}
+
+// Fatalf logs to FATAL log. Arguments are handled in the manner of fmt.Printf.
+func (z *ZapLogWrapper) Fatalf(format string, args ...interface{}) {
+ z.l.Fatalf(format, args...)
+}
+
+// Sync calls the zap logger's Sync method, and flushes any buffered log entries.
+// Applications should take care to call Sync before exiting.
+func (z *ZapLogWrapper) Sync() error {
+ return z.l.Sync()
+}
+
+// SetLevel set output log level.
+func (z *ZapLogWrapper) SetLevel(output string, level Level) {
+ z.l.SetLevel(output, level)
+}
+
+// GetLevel gets output log level.
+func (z *ZapLogWrapper) GetLevel(output string) Level {
+ return z.l.GetLevel(output)
+}
+
+// WithFields set some user defined data to logs, such as uid, imei, etc.
+// Use this function at the beginning of each request. The returned new Logger should be used to
+// print logs.
+// Fields must be paired.
+// Deprecated: use With instead.
+func (z *ZapLogWrapper) WithFields(fields ...string) Logger {
+ return z.l.WithFields(fields...)
+}
+
+// With add user defined fields to Logger. Fields support multiple values.
+func (z *ZapLogWrapper) With(fields ...Field) Logger {
+ return z.l.With(fields...)
+}
+
+// zapLog is a Logger implementation based on zaplogger.
+type zapLog struct {
+ levels []zap.AtomicLevel
+ logger *zap.Logger
+}
+
+// WithFields set some user defined data to logs, such as uid, imei, etc.
+// Use this function at the beginning of each request. The returned new Logger should be used to
+// print logs.
+// Fields must be paired.
+// Deprecated: use With instead.
+func (l *zapLog) WithFields(fields ...string) Logger {
+ zapFields := make([]zap.Field, len(fields)/2)
+ for i := range zapFields {
+ zapFields[i] = zap.String(fields[2*i], fields[2*i+1])
+ }
+
+ // By ZapLogWrapper proxy, we can add a layer to the debug series function calls, so that the
+ // caller information can be set correctly.
+ return &ZapLogWrapper{
+ l: &zapLog{
+ levels: l.levels,
+ logger: l.logger.With(zapFields...)}}
+}
+
+// With add user defined fields to Logger. Fields support multiple values.
+func (l *zapLog) With(fields ...Field) Logger {
+ zapFields := make([]zap.Field, len(fields))
+ for i := range fields {
+ zapFields[i] = zap.Any(fields[i].Key, fields[i].Value)
+ }
+
+ // By ZapLogWrapper proxy, we can add a layer to the debug series function calls, so that the
+ // caller information can be set correctly.
+ return &ZapLogWrapper{
+ l: &zapLog{
+ levels: l.levels,
+ logger: l.logger.With(zapFields...)}}
+}
+
+func getLogMsg(args ...interface{}) string {
+ msg := fmt.Sprint(args...)
+ return msg
+}
+
+func getLogMsgf(format string, args ...interface{}) string {
+ msg := fmt.Sprintf(format, args...)
+ return msg
+}
+
+// Trace logs to TRACE log. Arguments are handled in the manner of fmt.Print.
+func (l *zapLog) Trace(args ...interface{}) {
+ if l.logger.Core().Enabled(zapcore.DebugLevel) {
+ l.logger.Debug(getLogMsg(args...))
+ }
+}
+
+// Tracef logs to TRACE log. Arguments are handled in the manner of fmt.Printf.
+func (l *zapLog) Tracef(format string, args ...interface{}) {
+ if l.logger.Core().Enabled(zapcore.DebugLevel) {
+ l.logger.Debug(getLogMsgf(format, args...))
+ }
+}
+
+// Debug logs to DEBUG log. Arguments are handled in the manner of fmt.Print.
+func (l *zapLog) Debug(args ...interface{}) {
+ if l.logger.Core().Enabled(zapcore.DebugLevel) {
+ l.logger.Debug(getLogMsg(args...))
+ }
+}
+
+// Debugf logs to DEBUG log. Arguments are handled in the manner of fmt.Printf.
+func (l *zapLog) Debugf(format string, args ...interface{}) {
+ if l.logger.Core().Enabled(zapcore.DebugLevel) {
+ l.logger.Debug(getLogMsgf(format, args...))
+ }
+}
+
+// Info logs to INFO log. Arguments are handled in the manner of fmt.Print.
+func (l *zapLog) Info(args ...interface{}) {
+ if l.logger.Core().Enabled(zapcore.InfoLevel) {
+ l.logger.Info(getLogMsg(args...))
+ }
+}
+
+// Infof logs to INFO log. Arguments are handled in the manner of fmt.Printf.
+func (l *zapLog) Infof(format string, args ...interface{}) {
+ if l.logger.Core().Enabled(zapcore.InfoLevel) {
+ l.logger.Info(getLogMsgf(format, args...))
+ }
+}
+
+// Warn logs to WARNING log. Arguments are handled in the manner of fmt.Print.
+func (l *zapLog) Warn(args ...interface{}) {
+ if l.logger.Core().Enabled(zapcore.WarnLevel) {
+ l.logger.Warn(getLogMsg(args...))
+ }
+}
+
+// Warnf logs to WARNING log. Arguments are handled in the manner of fmt.Printf.
+func (l *zapLog) Warnf(format string, args ...interface{}) {
+ if l.logger.Core().Enabled(zapcore.WarnLevel) {
+ l.logger.Warn(getLogMsgf(format, args...))
+ }
+}
+
+// Error logs to ERROR log. Arguments are handled in the manner of fmt.Print.
+func (l *zapLog) Error(args ...interface{}) {
+ if l.logger.Core().Enabled(zapcore.ErrorLevel) {
+ l.logger.Error(getLogMsg(args...))
+ }
+}
+
+// Errorf logs to ERROR log. Arguments are handled in the manner of fmt.Printf.
+func (l *zapLog) Errorf(format string, args ...interface{}) {
+ if l.logger.Core().Enabled(zapcore.ErrorLevel) {
+ l.logger.Error(getLogMsgf(format, args...))
+ }
+}
+
+// Fatal logs to FATAL log. Arguments are handled in the manner of fmt.Print.
+func (l *zapLog) Fatal(args ...interface{}) {
+ if l.logger.Core().Enabled(zapcore.FatalLevel) {
+ l.logger.Fatal(getLogMsg(args...))
+ }
+}
+
+// Fatalf logs to FATAL log. Arguments are handled in the manner of fmt.Printf.
+func (l *zapLog) Fatalf(format string, args ...interface{}) {
+ if l.logger.Core().Enabled(zapcore.FatalLevel) {
+ l.logger.Fatal(getLogMsgf(format, args...))
+ }
+}
+
+// Sync calls the zap logger's Sync method, and flushes any buffered log entries.
+// Applications should take care to call Sync before exiting.
+func (l *zapLog) Sync() error {
+ return l.logger.Sync()
+}
+
+// SetLevel sets output log level.
+func (l *zapLog) SetLevel(output string, level Level) {
+ i, e := strconv.Atoi(output)
+ if e != nil {
+ return
+ }
+ if i < 0 || i >= len(l.levels) {
+ return
+ }
+ l.levels[i].SetLevel(levelToZapLevel[level])
+}
+
+// GetLevel gets output log level.
+func (l *zapLog) GetLevel(output string) Level {
+ i, e := strconv.Atoi(output)
+ if e != nil {
+ return LevelDebug
+ }
+ if i < 0 || i >= len(l.levels) {
+ return LevelDebug
+ }
+ return zapLevelToLevel[l.levels[i].Level()]
+}
diff --git a/pkg/README.md b/pkg/README.md
new file mode 100644
index 0000000..96236ab
--- /dev/null
+++ b/pkg/README.md
@@ -0,0 +1 @@
+external used code
\ No newline at end of file
diff --git a/pkg/common/protocol/grpc/keys.go b/pkg/common/protocol/grpc/keys.go
new file mode 100644
index 0000000..b627a25
--- /dev/null
+++ b/pkg/common/protocol/grpc/keys.go
@@ -0,0 +1,65 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package grpc
+
+var (
+ ENV = "env"
+ IDC = "idc"
+ SYS = "sys"
+ PID = "pid"
+ IP = "ip"
+ USERNAME = "username"
+ PASSWD = "passwd"
+ LANGUAGE = "language"
+ PROTOCOL_TYPE = "protocoltype"
+ PROTOCOL_VERSION = "protocolversion"
+ PROTOCOL_DESC = "protocoldesc"
+ SEQ_NUM = "seqnum"
+ UNIQUE_ID = "uniqueid"
+ TTL = "ttl"
+ PRODUCERGROUP = "producergroup"
+ TAG = "tag"
+ CONTENT_TYPE = "contenttype"
+)
+
+// request code
+var (
+ HTTP_PUSH_CLIENT_ASYNC = "105"
+)
+
+// protocol key
+var (
+ REQUEST_CODE = "code"
+ Version = "version"
+)
+
+// EventMeshInstanceKey
+var (
+ EVENTMESHCLUSTER = "eventmeshcluster"
+ EVENTMESHIP = "eventmeship"
+ EVENTMESHENV = "eventmeshenv"
+ EVENTMESHIDC = "eventmeshidc"
+)
+
+// PushMessageRequestBody
+var (
+ RANDOMNO = "randomNo"
+ TOPIC = "topic"
+ BIZSEQNO = "bizseqno"
+ UNIQUEID = "uniqueId"
+ CONTENT = "content"
+ EXTFIELDS = "extFields"
+)
diff --git a/pkg/common/protocol/grpc/message.go b/pkg/common/protocol/grpc/message.go
new file mode 100644
index 0000000..ce1029f
--- /dev/null
+++ b/pkg/common/protocol/grpc/message.go
@@ -0,0 +1,26 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package grpc
+
+import "github.com/apache/incubator-eventmesh/eventmesh-server-go/runtime/proto/pb"
+
+type SimpleMessageWrapper struct {
+ SimpleMessage *pb.SimpleMessage `json:"simpleMessage"`
+}
+
+type BatchMessageWrapper struct {
+ BatchMessage *pb.BatchMessage `json:"batchMessage"`
+}
diff --git a/pkg/common/protocol/grpc/status_code.go b/pkg/common/protocol/grpc/status_code.go
new file mode 100644
index 0000000..e6b730f
--- /dev/null
+++ b/pkg/common/protocol/grpc/status_code.go
@@ -0,0 +1,68 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package grpc
+
+import (
+ "fmt"
+ jsoniter "github.com/json-iterator/go"
+)
+
+type StatusCode struct {
+ RetCode string `json:"respCode"`
+ ErrMsg string `json:"respMsg"`
+}
+
+func (s *StatusCode) ToError() error {
+ return fmt.Errorf("code:%v, msg:%v", s.RetCode, s.ErrMsg)
+}
+
+func (s *StatusCode) ToJSONString() string {
+ str, _ := jsoniter.MarshalToString(s)
+ return str
+}
+
+var (
+ SUCCESS = &StatusCode{"0", "success"}
+ OVERLOAD = &StatusCode{"1", "eventMesh overload, try later, "}
+ EVENTMESH_REQUESTCODE_INVALID = &StatusCode{"2", "requestCode can't be null, or must be number, "}
+ EVENTMESH_SEND_SYNC_MSG_ERR = &StatusCode{"3", "eventMesh send rr msg err, "}
+ EVENTMESH_WAITING_RR_MSG_ERR = &StatusCode{"4", "eventMesh waiting rr msg err, "}
+ EVENTMESH_PROTOCOL_HEADER_ERR = &StatusCode{"6", "eventMesh protocol[header] err, "}
+ EVENTMESH_PROTOCOL_BODY_ERR = &StatusCode{"7", "eventMesh protocol[body] err, "}
+ EVENTMESH_STOP = &StatusCode{"8", "eventMesh will stop or had stopped, "}
+ EVENTMESH_REJECT_BY_PROCESSOR_ERROR = &StatusCode{"9", "eventMesh reject by processor error, "}
+ EVENTMESH_BATCH_PUBLISH_ERR = &StatusCode{"10", "eventMesh batch publish messages error, "}
+ EVENTMESH_SEND_BATCHLOG_MSG_ERR = &StatusCode{"17", "eventMesh send batchlog msg err, "}
+ EVENTMESH_BATCH_SPEED_OVER_LIMIT_ERR = &StatusCode{"11", "eventMesh batch msg speed over the limit, "}
+ EVENTMESH_PACKAGE_MSG_ERR = &StatusCode{"12", "eventMesh package msg err, "}
+ EVENTMESH_GROUP_PRODUCER_STOPED_ERR = &StatusCode{"13", "eventMesh group producer stopped, "}
+ EVENTMESH_SEND_ASYNC_MSG_ERR = &StatusCode{"14", "eventMesh send async msg err, "}
+ EVENTMESH_REPLY_MSG_ERR = &StatusCode{"15", "eventMesh reply msg err, "}
+ EVENTMESH_RUNTIME_ERR = &StatusCode{"16", "eventMesh runtime err, "}
+ EVENTMESH_SUBSCRIBE_ERR = &StatusCode{"17", "eventMesh subscribe err"}
+ EVENTMESH_UNSUBSCRIBE_ERR = &StatusCode{"18", "eventMesh unsubscribe err"}
+ EVENTMESH_HEARTBEAT_ERR = &StatusCode{"19", "eventMesh heartbeat err"}
+ EVENTMESH_ACL_ERR = &StatusCode{"20", "eventMesh acl err"}
+ EVENTMESH_SEND_MESSAGE_SPEED_OVER_LIMIT_ERR = &StatusCode{"21", "eventMesh send message speed over the limit err."}
+ EVENTMESH_REQUEST_REPLY_MSG_ERR = &StatusCode{"22", "eventMesh request reply msg err, "}
+ EVENTMESH_Plugin_NotFound_ERR = &StatusCode{"23", "eventMesh plugin not found err"}
+ EVENTMESH_Transfer_Protocol_ERR = &StatusCode{"24", "eventMesh plugin build event message err"}
+ EVENTMESH_Producer_Group_NotFound_ERR = &StatusCode{"25", "eventMesh producer not found err build event message err"}
+ EVENTMESH_Subscribe_Register_ERR = &StatusCode{"26", "eventMesh subscribe register err"}
+ EVENTMESH_Consumer_NotFound_ERR = &StatusCode{"27", "eventMesh consumer not found err build event message err"}
+ EVENTMESH_Restart_Consumer_ERR = &StatusCode{"28", "eventMesh restart consumer err"}
+ EVENTMESH_Heartbeat_Protocol_ERR = &StatusCode{"29", "eventMesh heartbeat but protocol not sub err"}
+)
diff --git a/pkg/common/protocol/tcp/pck.go b/pkg/common/protocol/tcp/pck.go
new file mode 100644
index 0000000..b13b02a
--- /dev/null
+++ b/pkg/common/protocol/tcp/pck.go
@@ -0,0 +1,97 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package tcp
+
+type Package struct {
+ Header Header
+ Body interface{}
+}
+
+type Header struct {
+ CMD Command `json:"cmd"`
+ Code int `json:"code"`
+ Desc string `json:"desc"`
+ Seq string `json:"seq"`
+}
+
+type Command int
+
+const (
+ HEARTBEAT_REQUEST Command = 0 //client send heartbeat packet to server
+
+ HEARTBEAT_RESPONSE Command = 1 //server response heartbeat packet of client
+
+ HELLO_REQUEST Command = 2 //client send handshake request to server
+
+ HELLO_RESPONSE Command = 3 //server response handshake request of client
+
+ //disconnection
+ CLIENT_GOODBYE_REQUEST Command = 4 //Notify server when client actively disconnects
+ CLIENT_GOODBYE_RESPONSE Command = 5
+ SERVER_GOODBYE_REQUEST Command = 6 //Notify client when server actively disconnects
+ SERVER_GOODBYE_RESPONSE Command = 7 //Client replies to server's active disconnection notification
+
+ //subscription management
+ SUBSCRIBE_REQUEST Command = 8 //Subscription request sent by client to server
+ SUBSCRIBE_RESPONSE Command = 9 //Server replies to client's subscription request
+ UNSUBSCRIBE_REQUEST Command = 10 //Unsubscribe request from client to server
+ UNSUBSCRIBE_RESPONSE Command = 11 //Server replies to client's unsubscribe request
+
+ //monitor
+ LISTEN_REQUEST Command = 12 //Request from client to server to start topic listening
+ LISTEN_RESPONSE Command = 13 //The server replies to the client's listening request
+
+ //RR
+ REQUEST_TO_SERVER Command = 14 //The client sends the RR request to the server
+ REQUEST_TO_CLIENT Command = 15 //The server pushes the RR request to the client
+ REQUEST_TO_CLIENT_ACK Command = 16 //After receiving RR request, the client sends ACK to the server
+ RESPONSE_TO_SERVER Command = 17 //The client sends the RR packet back to the server
+ RESPONSE_TO_CLIENT Command = 18 //The server pushes the RR packet back to the client
+ RESPONSE_TO_CLIENT_ACK Command = 19 //After receiving the return packet, the client sends ACK to the server
+
+ //Asynchronous events
+ ASYNC_MESSAGE_TO_SERVER Command = 20 //The client sends asynchronous events to the server
+ ASYNC_MESSAGE_TO_SERVER_ACK Command = 21 //After receiving the asynchronous event, the server sends ack to the client
+ ASYNC_MESSAGE_TO_CLIENT Command = 22 //The server pushes asynchronous events to the client
+ ASYNC_MESSAGE_TO_CLIENT_ACK Command = 23 //After the client receives the asynchronous event, the ACK is sent to the server
+
+ //radio broadcast
+ BROADCAST_MESSAGE_TO_SERVER Command = 24 //The client sends the broadcast message to the server
+ BROADCAST_MESSAGE_TO_SERVER_ACK Command = 25 //After receiving the broadcast message, the server sends ACK to the client
+ BROADCAST_MESSAGE_TO_CLIENT Command = 26 //The server pushes the broadcast message to the client
+ BROADCAST_MESSAGE_TO_CLIENT_ACK Command = 27 //After the client receives the broadcast message, the ACK is sent to the server
+
+ //Log reporting
+ SYS_LOG_TO_LOGSERVER Command = 28 //Business log reporting
+
+ //RMB tracking log reporting
+ TRACE_LOG_TO_LOGSERVER Command = 29 //RMB tracking log reporting
+
+ //Redirecting instruction
+ REDIRECT_TO_CLIENT Command = 30 //The server pushes the redirection instruction to the client
+
+ //service register
+ REGISTER_REQUEST Command = 31 //Client sends registration request to server
+ REGISTER_RESPONSE Command = 32 //The server sends the registration result to the client
+
+ //service unregister
+ UNREGISTER_REQUEST Command = 33 //The client sends a de registration request to the server
+ UNREGISTER_RESPONSE Command = 34 //The server will register the result to the client
+
+ //The client asks which EventMesh to recommend
+ RECOMMEND_REQUEST Command = 35 //Client sends recommendation request to server
+ RECOMMEND_RESPONSE Command = 36 //The server will recommend the results to the client
+)
diff --git a/pkg/naming/registry/instance.go b/pkg/naming/registry/instance.go
new file mode 100644
index 0000000..8499f7a
--- /dev/null
+++ b/pkg/naming/registry/instance.go
@@ -0,0 +1,34 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package registry
+
+import (
+ "fmt"
+)
+
+// Instance is the information of a instance.
+type Instance struct {
+ ServiceName string
+ Address string
+ Weight int
+ Clusters string
+ Metadata map[string]string
+}
+
+// String returns an abbreviation information of instance.
+func (n *Instance) String() string {
+ return fmt.Sprintf("service:%s, addr:%s", n.ServiceName, n.Address)
+}
diff --git a/pkg/naming/registry/registry.go b/pkg/naming/registry/registry.go
new file mode 100644
index 0000000..6faf8a0
--- /dev/null
+++ b/pkg/naming/registry/registry.go
@@ -0,0 +1,72 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// Package registry registers servers. A server report itself on start.
+package registry
+
+import (
+ "errors"
+ "sync"
+)
+
+// ErrNotImplement is the not implemented error.
+var ErrNotImplement = errors.New("not implement")
+
+// DefaultRegistry is the default registry.
+var DefaultRegistry Registry = &NoopRegistry{}
+
+// SetDefaultRegistry sets the default registry.
+func SetDefaultRegistry(r Registry) {
+ DefaultRegistry = r
+}
+
+// Registry is the interface that defines a register.
+type Registry interface {
+ Register(service string) error
+ Deregister(service string) error
+}
+
+var (
+ registries = make(map[string]Registry)
+ lock = sync.RWMutex{}
+)
+
+// Register registers a named registry. Each service has its own registry.
+func Register(name string, s Registry) {
+ lock.Lock()
+ registries[name] = s
+ lock.Unlock()
+}
+
+// Get gets a named registry.
+func Get(name string) Registry {
+ lock.RLock()
+ r := registries[name]
+ lock.RUnlock()
+ return r
+}
+
+// NoopRegistry is the noop registry.
+type NoopRegistry struct{}
+
+// Register always returns ErrNotImplement.
+func (noop *NoopRegistry) Register(service string) error {
+ return ErrNotImplement
+}
+
+// Deregister always return ErrNotImplement.
+func (noop *NoopRegistry) Deregister(service string) error {
+ return ErrNotImplement
+}
diff --git a/pkg/naming/selector/selector.go b/pkg/naming/selector/selector.go
new file mode 100644
index 0000000..3f99f89
--- /dev/null
+++ b/pkg/naming/selector/selector.go
@@ -0,0 +1,43 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package selector
+
+import "github.com/apache/incubator-eventmesh/eventmesh-server-go/pkg/naming/registry"
+
+// Selector is the interface that defines the selector.
+type Selector interface {
+ // Select gets a backend instance by service name.
+ Select(serviceName string) (*registry.Instance, error)
+}
+
+var (
+ selectors = make(map[string]Selector)
+)
+
+// Register registers a named Selector, such as l5, cmlb and tseer.
+func Register(name string, s Selector) {
+ selectors[name] = s
+}
+
+// Get gets a named Selector.
+func Get(name string) Selector {
+ s := selectors[name]
+ return s
+}
+
+func unregisterForTesting(name string) {
+ delete(selectors, name)
+}
diff --git a/pkg/util/id.go b/pkg/util/id.go
new file mode 100644
index 0000000..5a3833b
--- /dev/null
+++ b/pkg/util/id.go
@@ -0,0 +1,39 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package util
+
+import (
+ "fmt"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/pkg/version"
+ "strings"
+)
+
+func BuildMeshTcpClientID(sys, purpose, cluster string) string {
+ return fmt.Sprintf("%v-%v-%v-%v-%v",
+ strings.TrimSpace(sys),
+ strings.TrimSpace(purpose),
+ strings.TrimSpace(cluster),
+ strings.TrimSpace(version.Current),
+ strings.TrimSpace(PID()))
+}
+
+func BuildMeshClientID(group, cluster string) string {
+ return fmt.Sprintf("%v-(%v)-%v-%v",
+ strings.TrimSpace(group),
+ strings.TrimSpace(cluster),
+ strings.TrimSpace(version.Current),
+ strings.TrimSpace(PID()))
+}
diff --git a/pkg/util/id_test.go b/pkg/util/id_test.go
new file mode 100644
index 0000000..61d15a6
--- /dev/null
+++ b/pkg/util/id_test.go
@@ -0,0 +1,94 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package util
+
+import (
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/pkg/version"
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestBuildMeshClientID(t *testing.T) {
+ type args struct {
+ group string
+ cluster string
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ name: "build client id",
+ args: args{
+ group: "test-group",
+ cluster: "idc",
+ },
+ want: "test-group-(idc)-" + version.Current + "-" + PID(),
+ },
+ {
+ name: "build client id with upper",
+ args: args{
+ group: "Test-group",
+ cluster: "IDC",
+ },
+ want: "Test-group-(IDC)-" + version.Current + "-" + PID(),
+ },
+ {
+ name: "build client id wit space",
+ args: args{
+ group: " Test-group ",
+ cluster: "idc ",
+ },
+ want: "Test-group-(idc)-" + version.Current + "-" + PID(),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := BuildMeshClientID(tt.args.group, tt.args.cluster); got != tt.want {
+ t.Errorf("BuildMeshClientID() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestBuildMeshTcpClientID(t *testing.T) {
+ type args struct {
+ sys string
+ purpose string
+ cluster string
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ }{
+ {
+ name: "uniq mesh tcp client id",
+ args: args{
+ sys: "1234",
+ purpose: "test",
+ cluster: "c1",
+ },
+ want: "1234-test-c1-" + version.Current + "-" + PID(),
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ assert.Equalf(t, tt.want, BuildMeshTcpClientID(tt.args.sys, tt.args.purpose, tt.args.cluster), "BuildMeshTcpClientID(%v, %v, %v)", tt.args.sys, tt.args.purpose, tt.args.cluster)
+ })
+ }
+}
diff --git a/pkg/util/ip.go b/pkg/util/ip.go
new file mode 100644
index 0000000..243bb60
--- /dev/null
+++ b/pkg/util/ip.go
@@ -0,0 +1,43 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package util
+
+import (
+ "errors"
+ "net"
+)
+
+var ip string
+
+func init() {
+ addresses, err := net.InterfaceAddrs()
+ if err != nil {
+ panic(err)
+ }
+ for _, address := range addresses {
+ if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
+ if ipnet.IP.To4() != nil {
+ ip = ipnet.IP.String()
+ return
+ }
+ }
+ }
+ panic(errors.New("can not find the client ip address"))
+}
+
+func GetIP() string {
+ return ip
+}
diff --git a/pkg/util/pid.go b/pkg/util/pid.go
new file mode 100644
index 0000000..97208d9
--- /dev/null
+++ b/pkg/util/pid.go
@@ -0,0 +1,32 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package util
+
+import (
+ "fmt"
+ "os"
+)
+
+var _pid string
+
+func init() {
+ _pid = fmt.Sprintf("%v", os.Getpid())
+}
+
+// PID return the current process id
+func PID() string {
+ return _pid
+}
diff --git a/pkg/util/pid_test.go b/pkg/util/pid_test.go
new file mode 100644
index 0000000..42483d9
--- /dev/null
+++ b/pkg/util/pid_test.go
@@ -0,0 +1,37 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package util
+
+import (
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestPID(t *testing.T) {
+ tests := []struct {
+ name string
+ }{
+ {
+ name: "pid",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := PID()
+ assert.NotEmpty(t, got)
+ })
+ }
+}
diff --git a/pkg/version/version.go b/pkg/version/version.go
new file mode 100644
index 0000000..2a7828f
--- /dev/null
+++ b/pkg/version/version.go
@@ -0,0 +1,20 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package version
+
+var (
+ Current = "v0.0.1"
+)
diff --git a/plugin/connector/connector.go b/plugin/connector/connector.go
new file mode 100644
index 0000000..46d3b53
--- /dev/null
+++ b/plugin/connector/connector.go
@@ -0,0 +1,122 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package connector
+
+import (
+ "context"
+ "time"
+
+ ce "github.com/cloudevents/sdk-go/v2"
+
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+)
+
+const (
+ PluginType = "connector"
+)
+
+// Factory plugin factory of consumer/producer/resource
+type Factory interface {
+ plugin.Plugin
+
+ GetConsumer() (Consumer, error)
+ GetProducer() (Producer, error)
+ GetResource() (Resource, error)
+}
+
+// EventMeshAction commit action of message consume
+type EventMeshAction uint
+
+const (
+ CommitMessage EventMeshAction = iota
+ ReconsumeLater
+ ManualAck
+)
+
+// Consumer interface of consumer
+// all the consumers implement this interface should implement a corresponding factory and do plugin registration first.
+type Consumer interface {
+ LifeCycle
+
+ InitConsumer(properties map[string]string) error
+ UpdateOffset(ctx context.Context, events []*ce.Event) error
+ Subscribe(topicName string) error
+ Unsubscribe(topicName string) error
+ RegisterEventListener(listener *EventListener)
+}
+
+// Producer interface of producer
+// all the producers implement this interface should implement a corresponding factory and do plugin registration first.
+type Producer interface {
+ LifeCycle
+
+ InitProducer(properties map[string]string) error
+ Publish(ctx context.Context, event *ce.Event, callback *SendCallback) error
+ SendOneway(ctx context.Context, event *ce.Event) error
+ Request(ctx context.Context, event *ce.Event, callback *RequestReplyCallback, timeout time.Duration) error
+ Reply(ctx context.Context, event *ce.Event, callback *SendCallback) error
+ CheckTopicExist(topicName string) (bool, error)
+ SetExtFields() error
+}
+
+// Resource interface of resource service
+// all the resources implement this interface should implement a corresponding factory and do plugin registration first.
+type Resource interface {
+ Init() error
+ Release() error
+}
+
+// LifeCycle general life cycle interface for all connectors
+type LifeCycle interface {
+ IsStarted() bool
+ IsClosed() bool
+ Start() error
+ Shutdown() error
+}
+
+// SendCallback send callback handler of function Publish
+type SendCallback struct {
+ OnSuccess func(result *SendResult)
+ OnError func(result *ErrorResult)
+}
+
+// RequestReplyCallback request/reply callback handler of function Request and Reply
+type RequestReplyCallback struct {
+ OnSuccess func(event *ce.Event)
+ OnError func(result *ErrorResult)
+}
+
+type SendResult struct {
+ MessageId string
+ Topic string
+ Err error
+}
+
+type ErrorResult struct {
+ Topic string
+ Err error
+}
+
+// EventListener message consume handler
+type EventListener struct {
+ Consume ConsumeFunc
+}
+
+// CommitFunc user can commit message through this function in the consuming logic
+type CommitFunc func(action EventMeshAction) error
+
+// ConsumeFunc custom message consuming logic
+type ConsumeFunc func(event *ce.Event, commitFunc CommitFunc) error
diff --git a/plugin/connector/rocketmq/client/rocketmq_consumer.go b/plugin/connector/rocketmq/client/rocketmq_consumer.go
new file mode 100644
index 0000000..2cc867b
--- /dev/null
+++ b/plugin/connector/rocketmq/client/rocketmq_consumer.go
@@ -0,0 +1,159 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package client
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/constants"
+ "github.com/apache/rocketmq-client-go/v2"
+ "github.com/apache/rocketmq-client-go/v2/consumer"
+ "github.com/apache/rocketmq-client-go/v2/primitive"
+ "strconv"
+ "strings"
+)
+
+type SubscribeFunc func(context.Context, ...*primitive.MessageExt) (consumer.ConsumeResult, error)
+
+// RocketMQConsumer RocketMQ consumer interface
+type RocketMQConsumer interface {
+ Start() error
+ Shutdown() error
+ Subscribe(topic string, selector consumer.MessageSelector, f SubscribeFunc) error
+ Unsubscribe(topic string) error
+ Suspend()
+ Resume()
+ IsBroadCasting() bool
+}
+
+// RocketMQConsumerWrapper consumer wrapper of RocketMQ client
+type RocketMQConsumerWrapper struct {
+ options []consumer.Option
+ consumer rocketmq.PushConsumer
+ messageModel consumer.MessageModel
+}
+
+// NewRocketMQConsumerWrapper get a consumer wrapper of RocketMQ client
+func NewRocketMQConsumerWrapper(properties map[string]string) (RocketMQConsumer, error) {
+ consumerWrapper := &RocketMQConsumerWrapper{messageModel: consumer.Clustering}
+
+ options, err := consumerWrapper.getConsumerOptionsFromProperties(properties)
+ if err != nil {
+ return nil, err
+ }
+
+ rocketMQConsumer, err := rocketmq.NewPushConsumer(options...)
+ if err != nil {
+ return nil, err
+ }
+ consumerWrapper.consumer = rocketMQConsumer
+
+ return consumerWrapper, nil
+}
+
+// Start wrapper start function
+func (r *RocketMQConsumerWrapper) Start() error {
+ return r.consumer.Start()
+}
+
+// Shutdown wrapper shutdown function
+func (r *RocketMQConsumerWrapper) Shutdown() error {
+ return r.consumer.Shutdown()
+}
+
+// Subscribe wrapper subscribe function
+func (r *RocketMQConsumerWrapper) Subscribe(topic string, selector consumer.MessageSelector, f SubscribeFunc) error {
+ return r.consumer.Subscribe(topic, selector, f)
+}
+
+// Unsubscribe wrapper unsubscribe function
+func (r *RocketMQConsumerWrapper) Unsubscribe(topic string) error {
+ return r.consumer.Unsubscribe(topic)
+}
+
+// Suspend wrapper suspend function
+func (r *RocketMQConsumerWrapper) Suspend() {
+ r.consumer.Suspend()
+}
+
+// Resume wrapper resume function
+func (r *RocketMQConsumerWrapper) Resume() {
+ r.consumer.Resume()
+}
+
+// IsBroadCasting check if consumer mode is broadcasting
+func (r *RocketMQConsumerWrapper) IsBroadCasting() bool {
+ return r.messageModel == consumer.BroadCasting
+}
+
+// getConsumerOptionsFromProperties convert properties map to client options
+func (r *RocketMQConsumerWrapper) getConsumerOptionsFromProperties(properties map[string]string) ([]consumer.Option, error) {
+ clientConfig, err := getClientConfigFromProperties(properties)
+ if clientConfig == nil {
+ return nil, err
+ }
+ options := make([]consumer.Option, 0)
+
+ accessPoints := clientConfig.AccessPoints
+ if len(accessPoints) == 0 {
+ return nil, errors.New("fail to parse rocketmq consumer config, invalid access points")
+ }
+
+ // name server address
+ options = append(options, consumer.WithNameServer(strings.Split(accessPoints, ",")))
+
+ // max reconsume times
+ if len(clientConfig.MaxReconsumeTimes) != 0 {
+ maxReconsumeTimes, err := strconv.ParseInt(clientConfig.MaxReconsumeTimes, 10, 32)
+ if err == nil {
+ options = append(options, consumer.WithMaxReconsumeTimes(int32(maxReconsumeTimes)))
+ }
+ }
+
+ // consume message model
+ isBroadCasting := false
+ if len(clientConfig.MessageModel) != 0 {
+ switch clientConfig.MessageModel {
+ case consumer.BroadCasting.String():
+ options = append(options, consumer.WithConsumerModel(consumer.BroadCasting))
+ isBroadCasting = true
+ r.messageModel = consumer.BroadCasting
+ default:
+ options = append(options, consumer.WithConsumerModel(consumer.Clustering))
+ r.messageModel = consumer.Clustering
+ }
+ }
+
+ // consumer group
+ if len(clientConfig.ConsumerGroup) == 0 {
+ return nil, errors.New("fail to create rocketmq consumer, consumer group is empty")
+ }
+ consumerGroup := clientConfig.ConsumerGroup
+ if isBroadCasting {
+ consumerGroup = fmt.Sprintf("%s-%s", constants.ConsumerGroupBroadcastPrefix, consumerGroup)
+ }
+ options = append(options, consumer.WithGroupName(clientConfig.ConsumerGroup))
+
+ // TODO consumeTimeout config, currently rocket mq go client doesn't support
+
+ // instance name
+ if len(clientConfig.InstanceName) != 0 {
+ options = append(options, consumer.WithInstance(clientConfig.InstanceName))
+ }
+
+ return options, nil
+}
diff --git a/plugin/connector/rocketmq/client/rocketmq_producer.go b/plugin/connector/rocketmq/client/rocketmq_producer.go
new file mode 100644
index 0000000..934bcdb
--- /dev/null
+++ b/plugin/connector/rocketmq/client/rocketmq_producer.go
@@ -0,0 +1,168 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package client
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/config"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/utils"
+ "github.com/apache/rocketmq-client-go/v2"
+ "github.com/apache/rocketmq-client-go/v2/primitive"
+ "github.com/apache/rocketmq-client-go/v2/producer"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// RocketMQProducer RocketMQ producer interface
+type RocketMQProducer interface {
+ Start() error
+ Shutdown() error
+ SendSync(ctx context.Context, msg ...*primitive.Message) (*primitive.SendResult, error)
+ SendAsync(ctx context.Context, mq func(ctx context.Context, result *primitive.SendResult, err error),
+ msg ...*primitive.Message) error
+ SendOneWay(ctx context.Context, msg ...*primitive.Message) error
+ Request(ctx context.Context, ttl time.Duration, msg *primitive.Message) (*primitive.Message, error)
+ RequestAsync(ctx context.Context, ttl time.Duration, callback func(ctx context.Context,
+ msg *primitive.Message, err error), msg *primitive.Message) error
+}
+
+// RocketMQProducerWrapper producer wrapper of RocketMQ client
+type RocketMQProducerWrapper struct {
+ options []producer.Option
+ producer rocketmq.Producer
+}
+
+// NewRocketMQProducerWrapper get a producer wrapper of RocketMQ client
+func NewRocketMQProducerWrapper(properties map[string]string) (*RocketMQProducerWrapper, error) {
+ options, err := getOptionsProducerFromProperties(properties)
+ if err != nil {
+ return nil, err
+ }
+
+ rocketmqProducer, err := rocketmq.NewProducer(options...)
+ if err != nil {
+ return nil, err
+ }
+
+ return &RocketMQProducerWrapper{
+ options: options,
+ producer: rocketmqProducer,
+ }, nil
+}
+
+// Start wrapper start function
+func (r *RocketMQProducerWrapper) Start() error {
+ return r.producer.Start()
+}
+
+// Shutdown wrapper shutdown function
+func (r *RocketMQProducerWrapper) Shutdown() error {
+ return r.producer.Shutdown()
+}
+
+// SendSync wrapper send sync function
+func (r *RocketMQProducerWrapper) SendSync(ctx context.Context, msg ...*primitive.Message) (*primitive.SendResult, error) {
+ return r.producer.SendSync(ctx, msg...)
+}
+
+// SendAsync wrapper send async function
+func (r *RocketMQProducerWrapper) SendAsync(ctx context.Context, mq func(ctx context.Context,
+ result *primitive.SendResult, err error), msg ...*primitive.Message) error {
+ return r.producer.SendAsync(ctx, mq, msg...)
+}
+
+// SendOneWay wrapper send one way function
+func (r *RocketMQProducerWrapper) SendOneWay(ctx context.Context, msg ...*primitive.Message) error {
+ return r.producer.SendOneWay(ctx, msg...)
+}
+
+// Request wrapper request function
+func (r *RocketMQProducerWrapper) Request(ctx context.Context, ttl time.Duration,
+ msg *primitive.Message) (*primitive.Message, error) {
+ return r.producer.Request(ctx, ttl, msg)
+}
+
+// RequestAsync wrapper request async function
+func (r *RocketMQProducerWrapper) RequestAsync(ctx context.Context, ttl time.Duration,
+ callback func(ctx context.Context, msg *primitive.Message, err error), msg *primitive.Message) error {
+ return r.producer.RequestAsync(ctx, ttl, callback, msg)
+}
+
+// getOptionsProducerFromProperties convert properties map to client options
+func getOptionsProducerFromProperties(properties map[string]string) ([]producer.Option, error) {
+ clientConfig, err := getClientConfigFromProperties(properties)
+ if clientConfig == nil {
+ return nil, err
+ }
+ options := make([]producer.Option, 0)
+
+ accessPoints := clientConfig.AccessPoints
+ if len(accessPoints) == 0 {
+ return nil, errors.New("fail to parse rocketmq producer config, invalid access points")
+ }
+
+ // name server address
+ options = append(options, producer.WithNameServer(strings.Split(accessPoints, ",")))
+
+ // instance name
+ producerId := utils.GetInstanceName()
+ options = append(options, producer.WithInstanceName(producerId))
+ clientConfig.InstanceName = producerId
+
+ // producer group name
+ options = append(options, producer.WithGroupName(clientConfig.ProducerGroupName))
+
+ // send msg timeout
+ if len(clientConfig.SendMsgTimeout) != 0 {
+ sendMsgTimeout, err := strconv.Atoi(clientConfig.SendMsgTimeout)
+ if err == nil {
+ options = append(options, producer.WithSendMsgTimeout(time.Duration(sendMsgTimeout)*time.Millisecond))
+ }
+ }
+
+ // producer retry time
+ if len(clientConfig.ProducerRetryTimes) != 0 {
+ producerRetryTimes, err := strconv.Atoi(clientConfig.ProducerRetryTimes)
+ if err == nil {
+ options = append(options, producer.WithRetry(producerRetryTimes))
+ }
+ }
+
+ // producer compress message body threshold
+ if len(clientConfig.CompressMsgBodyThreshold) != 0 {
+ compressMsgBodyThreshold, err := strconv.Atoi(clientConfig.CompressMsgBodyThreshold)
+ if err == nil {
+ options = append(options, producer.WithCompressMsgBodyOverHowmuch(compressMsgBodyThreshold))
+ }
+ }
+ return options, nil
+}
+
+func getClientConfigFromProperties(properties map[string]string) (*config.ClientConfig, error) {
+ arr, err := json.Marshal(properties)
+ if err != nil {
+ return nil, err
+ }
+ clientConfig := &config.ClientConfig{}
+ err = json.Unmarshal(arr, clientConfig)
+ if err != nil {
+ return nil, err
+ }
+ return clientConfig, nil
+}
diff --git a/plugin/connector/rocketmq/config/client_config.go b/plugin/connector/rocketmq/config/client_config.go
new file mode 100644
index 0000000..12b291a
--- /dev/null
+++ b/plugin/connector/rocketmq/config/client_config.go
@@ -0,0 +1,41 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package config
+
+// ClientConfig config of RocketMQ client
+type ClientConfig struct {
+ // AccessPoints addresses of rocketmq name servers, join by ";"
+ AccessPoints string `json:"access_points"`
+ // Namespace client namespace
+ Namespace string `json:"namespace"`
+ // InstanceName client instance name
+ InstanceName string `json:"instance_name"`
+ // ProducerGroupName producer client group name
+ ProducerGroupName string `json:"group_name"`
+ // SendMsgTimeout producer send message timeout (ms), default 3000ms
+ SendMsgTimeout string `json:"send_msg_timeout"`
+ // ProducerRetryTimes producer retry time
+ ProducerRetryTimes string `json:"producer_retry_times"`
+ // CompressMsgBodyThreshold producer compress message body threshold
+ CompressMsgBodyThreshold string `json:"compress_msg_body_threshold"`
+
+ // ConsumerGroup consumer group
+ ConsumerGroup string `json:"consumer_group"`
+ // MaxReconsumeTimes max reconsume times
+ MaxReconsumeTimes string `json:"max_reconsume_times"`
+ // MessageModel message model of consumer, can be BROADCASTING or CLUSTERING
+ MessageModel string `json:"message_model"`
+}
diff --git a/plugin/connector/rocketmq/constants/message_constants.go b/plugin/connector/rocketmq/constants/message_constants.go
new file mode 100644
index 0000000..371cbec
--- /dev/null
+++ b/plugin/connector/rocketmq/constants/message_constants.go
@@ -0,0 +1,25 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package constants
+
+const (
+ CloudEventMessageType = "eventmeshmessage"
+ RocketMQMessageContentTypeProperties = "datacontenttype"
+ MessagePropertySeparator = "99"
+ ConsumerGroupBroadcastPrefix = "broadcast"
+ PropertyMessageBornTimestamp = "borntimestamp"
+ PropertyMessageStoreTimestamp = "storetimestamp"
+)
diff --git a/plugin/connector/rocketmq/constants/rocketmq_message_properties.go b/plugin/connector/rocketmq/constants/rocketmq_message_properties.go
new file mode 100644
index 0000000..3b0b8b0
--- /dev/null
+++ b/plugin/connector/rocketmq/constants/rocketmq_message_properties.go
@@ -0,0 +1,61 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package constants
+
+import (
+ gs "github.com/deckarep/golang-set/v2"
+)
+
+// RocketMQMessageProperties Properties of RocketMQ Message
+var RocketMQMessageProperties gs.Set[string]
+
+func init() {
+ RocketMQMessageProperties = gs.NewSet[string]()
+ RocketMQMessageProperties.Add(" ")
+ RocketMQMessageProperties.Add("KEYS")
+ RocketMQMessageProperties.Add("TAGS")
+ RocketMQMessageProperties.Add("WAIT")
+ RocketMQMessageProperties.Add("DELAY")
+ RocketMQMessageProperties.Add("RETRY_TOPIC")
+ RocketMQMessageProperties.Add("REAL_TOPIC")
+ RocketMQMessageProperties.Add("REAL_QID")
+ RocketMQMessageProperties.Add("TRAN_MSG")
+ RocketMQMessageProperties.Add("PGROUP")
+ RocketMQMessageProperties.Add("MIN_OFFSET")
+ RocketMQMessageProperties.Add("MAX_OFFSET")
+ RocketMQMessageProperties.Add("BUYER_ID")
+ RocketMQMessageProperties.Add("ORIGIN_MESSAGE_ID")
+ RocketMQMessageProperties.Add("TRANSFER_FLAG")
+ RocketMQMessageProperties.Add("CORRECTION_FLAG")
+ RocketMQMessageProperties.Add("MQ2_FLAG")
+ RocketMQMessageProperties.Add("RECONSUME_TIME")
+ RocketMQMessageProperties.Add("MSG_REGION")
+ RocketMQMessageProperties.Add("TRACE_ON")
+ RocketMQMessageProperties.Add("UNIQ_KEY")
+ RocketMQMessageProperties.Add("MAX_RECONSUME_TIMES")
+ RocketMQMessageProperties.Add("CONSUME_START_TIME")
+ RocketMQMessageProperties.Add("TRAN_PREPARED_QUEUE_OFFSET")
+ RocketMQMessageProperties.Add("TRANSACTION_CHECK_TIMES")
+ RocketMQMessageProperties.Add("CHECK_IMMUNITY_TIME_IN_SECONDS")
+ RocketMQMessageProperties.Add("SHARDING_KEY")
+ RocketMQMessageProperties.Add("__transactionId__")
+ RocketMQMessageProperties.Add("CORRELATION_ID")
+ RocketMQMessageProperties.Add("REPLY_TO_CLIENT")
+ RocketMQMessageProperties.Add("TTL")
+ RocketMQMessageProperties.Add("ARRIVE_TIME")
+ RocketMQMessageProperties.Add("MSG_TYPE")
+ RocketMQMessageProperties.Add("CLUSTER")
+}
diff --git a/plugin/connector/rocketmq/consumer.go b/plugin/connector/rocketmq/consumer.go
new file mode 100644
index 0000000..d7fcc77
--- /dev/null
+++ b/plugin/connector/rocketmq/consumer.go
@@ -0,0 +1,221 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package rocketmq
+
+import (
+ "context"
+ "errors"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/client"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/constants"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/convert"
+ "github.com/apache/rocketmq-client-go/v2/consumer"
+ "github.com/apache/rocketmq-client-go/v2/primitive"
+ ce "github.com/cloudevents/sdk-go/v2"
+ "strconv"
+ "sync"
+)
+
+type Consumer struct {
+ rocketMQConsumer client.RocketMQConsumer
+ started bool
+ mutex sync.Mutex
+ listener *connector.EventListener
+ subscribeHandler SubscribeHandler
+}
+
+// NewConsumer get new consumer, needs to be Initiated before using
+func NewConsumer() *Consumer {
+ return &Consumer{}
+}
+
+// InitConsumer init consumer by properties
+func (c *Consumer) InitConsumer(properties map[string]string) error {
+ consumer, err := client.NewRocketMQConsumerWrapper(properties)
+ if err != nil {
+ return err
+ }
+ c.rocketMQConsumer = consumer
+ if c.rocketMQConsumer.IsBroadCasting() {
+ c.subscribeHandler = &BroadCastingMessageSubscribeHandler{consumer: c}
+ } else {
+ c.subscribeHandler = &ClusteringMessageSubscribeHandler{consumer: c}
+ }
+ return nil
+}
+
+// IsStarted check if consumer is started
+func (c *Consumer) IsStarted() bool {
+ return c.rocketMQConsumer != nil && c.started
+}
+
+// IsClosed check if consumer is closed
+func (c *Consumer) IsClosed() bool {
+ return c.rocketMQConsumer != nil && !c.started
+}
+
+// Start make consumer started
+func (c *Consumer) Start() error {
+ if c.rocketMQConsumer == nil {
+ return errors.New("start rocketmq consumer fail, producer should be initiated first")
+ }
+ c.mutex.Lock()
+ defer c.mutex.Unlock()
+ if !c.started {
+ err := c.rocketMQConsumer.Start()
+ if err != nil {
+ return err
+ }
+ c.started = true
+ }
+ return nil
+}
+
+// Shutdown terminate the consumer
+func (c *Consumer) Shutdown() error {
+ if c.rocketMQConsumer == nil {
+ return errors.New("shutdown rocketmq consumer fail, producer should be initiated first")
+ }
+ c.mutex.Lock()
+ defer c.mutex.Unlock()
+ if c.started {
+ err := c.rocketMQConsumer.Shutdown()
+ if err != nil {
+ return err
+ }
+ c.started = false
+ }
+ return nil
+}
+
+// UpdateOffset always return error, since currently RocketMQ client doesn't support manual offset updating
+func (c *Consumer) UpdateOffset(ctx context.Context, events []*ce.Event) error {
+ // TODO support offset update
+ return errors.New("fail to update offset, currently RocketMQ client doesn't support manual offset updating")
+}
+
+// Subscribe subscribe topic
+func (c *Consumer) Subscribe(topicName string) error {
+ return c.rocketMQConsumer.Subscribe(topicName, consumer.MessageSelector{}, c.subscribeHandler.handle)
+}
+
+// Unsubscribe unsubscribe topic
+func (c *Consumer) Unsubscribe(topicName string) error {
+ return c.rocketMQConsumer.Unsubscribe(topicName)
+}
+
+// RegisterEventListener listener's Consume function will be called when message is being consumed
+func (c *Consumer) RegisterEventListener(listener *connector.EventListener) {
+ c.listener = listener
+}
+
+// SubscribeHandler interface of message consume handler
+type SubscribeHandler interface {
+ handle(ctx context.Context, msg ...*primitive.MessageExt) (consumer.ConsumeResult, error)
+ getMessageModel() consumer.MessageModel
+}
+
+// ClusteringMessageSubscribeHandler message consume handler of Clustering mode
+type ClusteringMessageSubscribeHandler struct {
+ consumer *Consumer
+}
+
+func (h *ClusteringMessageSubscribeHandler) getMessageModel() consumer.MessageModel {
+ return consumer.Clustering
+}
+
+func (h *ClusteringMessageSubscribeHandler) handle(ctx context.Context, msg ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
+ if len(msg) == 0 {
+ return consumer.ConsumeSuccess, nil
+ }
+
+ messageExt := msg[0]
+ messageExt.WithProperty(constants.PropertyMessageBornTimestamp,
+ strconv.FormatInt(messageExt.BornTimestamp, 10))
+ messageExt.WithProperty(constants.PropertyMessageStoreTimestamp,
+ strconv.FormatInt(messageExt.StoreTimestamp, 10))
+
+ message := &messageExt.Message
+ convert.TransferMessageSystemProperties(message)
+
+ event, err := convert.NewRocketMQMessageReader(message).ToCloudEvent(context.Background())
+ if err != nil {
+ return consumer.ConsumeSuccess, nil
+ }
+
+ consumeResult := consumer.ConsumeSuccess
+ commitFunction := func(action connector.EventMeshAction) error {
+ switch action {
+ case connector.CommitMessage:
+ consumeResult = consumer.ConsumeSuccess
+ case connector.ReconsumeLater:
+ consumeResult = consumer.ConsumeRetryLater
+ case connector.ManualAck:
+ // currently, RocketMQ go client doesn't support manual offset updating, so just commit message here
+ // TODO support manual offset updating
+ consumeResult = consumer.ConsumeSuccess
+ }
+ return nil
+ }
+ h.consumer.listener.Consume(event, commitFunction)
+ return consumeResult, nil
+}
+
+// BroadCastingMessageSubscribeHandler message consume handler of BroadCasting mode
+type BroadCastingMessageSubscribeHandler struct {
+ consumer *Consumer
+}
+
+func (h *BroadCastingMessageSubscribeHandler) getMessageModel() consumer.MessageModel {
+ return consumer.BroadCasting
+}
+
+func (h *BroadCastingMessageSubscribeHandler) handle(ctx context.Context, msg ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
+ if len(msg) == 0 {
+ return consumer.ConsumeSuccess, nil
+ }
+
+ messageExt := msg[0]
+ messageExt.WithProperty(constants.PropertyMessageBornTimestamp,
+ strconv.FormatInt(messageExt.BornTimestamp, 10))
+ messageExt.WithProperty(constants.PropertyMessageStoreTimestamp,
+ strconv.FormatInt(messageExt.StoreTimestamp, 10))
+
+ message := &messageExt.Message
+ convert.TransferMessageSystemProperties(message)
+
+ event, err := convert.NewRocketMQMessageReader(message).ToCloudEvent(context.Background())
+ if err != nil {
+ return consumer.ConsumeSuccess, nil
+ }
+
+ consumeResult := consumer.ConsumeSuccess
+ commitFunction := func(action connector.EventMeshAction) error {
+ switch action {
+ case connector.CommitMessage:
+ consumeResult = consumer.ConsumeSuccess
+ case connector.ReconsumeLater:
+ consumeResult = consumer.ConsumeRetryLater
+ case connector.ManualAck:
+ // currently, RocketMQ go client doesn't support manual offset updating, so just commit message here
+ // TODO support manual offset updating
+ consumeResult = consumer.ConsumeSuccess
+ }
+ return nil
+ }
+ h.consumer.listener.Consume(event, commitFunction)
+ return consumeResult, nil
+}
diff --git a/plugin/connector/rocketmq/consumer_test.go b/plugin/connector/rocketmq/consumer_test.go
new file mode 100644
index 0000000..a801cca
--- /dev/null
+++ b/plugin/connector/rocketmq/consumer_test.go
@@ -0,0 +1,130 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package rocketmq
+
+import (
+ "context"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/client"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/convert"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/mock"
+ rmq "github.com/apache/rocketmq-client-go/v2/consumer"
+ "github.com/apache/rocketmq-client-go/v2/primitive"
+ ce "github.com/cloudevents/sdk-go/v2"
+ "github.com/golang/mock/gomock"
+ "github.com/stretchr/testify/require"
+ "testing"
+)
+
+func GetConsumerWithMockClient(ctl *gomock.Controller) *Consumer {
+ consumer := NewConsumer()
+ consumer.InitConsumer(make(map[string]string))
+ mockRocketMQConsumer := mock.NewMockRocketMQConsumer(ctl)
+ mockRocketMQConsumer.EXPECT().Start().Return(nil)
+ mockRocketMQConsumer.EXPECT().Shutdown().Return(nil)
+ consumer.rocketMQConsumer = mockRocketMQConsumer
+ consumer.subscribeHandler = &ClusteringMessageSubscribeHandler{consumer: consumer}
+ return consumer
+}
+
+func TestConsumer_Start(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+ consumer := GetConsumerWithMockClient(ctrl)
+ consumer.Start()
+ require.True(t, consumer.IsStarted())
+ consumer.Shutdown()
+}
+
+func TestConsumer_Shutdown(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+ consumer := GetConsumerWithMockClient(ctrl)
+ consumer.Start()
+ consumer.Shutdown()
+ require.False(t, consumer.IsStarted())
+}
+
+func TestConsumer_Subscribe(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+ consumer := GetConsumerWithMockClient(ctrl)
+ consumer.Start()
+
+ listener := &connector.EventListener{
+ Consume: func(event *ce.Event, commitFunc connector.CommitFunc) error {
+ require.True(t, event.Subject() == "TopicTest")
+ commitFunc(connector.CommitMessage)
+ return nil
+ },
+ }
+
+ consumer.RegisterEventListener(listener)
+
+ consumer.rocketMQConsumer.(*mock.MockRocketMQConsumer).EXPECT().
+ Subscribe(gomock.Any(), gomock.Any(), gomock.Any()).
+ Do(
+ func(topic string, selector rmq.MessageSelector, f client.SubscribeFunc) {
+ messageExt := &primitive.MessageExt{
+ Message: *GetFullMessage(),
+ MsgId: "MockMsgId",
+ OffsetMsgId: "MockOffsetMsgId",
+ }
+ result, err := f(context.Background(), messageExt)
+ require.False(t, err != nil)
+ require.True(t, result == rmq.ConsumeSuccess)
+ },
+ )
+
+ consumer.Subscribe("TopicTest")
+ consumer.Shutdown()
+}
+
+//func TestConsumer_Subscribe_Broker(t *testing.T) {
+// properties := make(map[string]string)
+// properties["access_points"] = "127.0.0.1:9876"
+// properties["consumer_group"] = "test_group"
+// consumer := NewConsumer()
+// consumer.InitConsumer(properties)
+// consumer.Start()
+//
+// listener := &connector.EventListener{
+// Consume: func(event *ce.Event, commitFunc connector.CommitFunc) error {
+// fmt.Println(event)
+// commitFunc(connector.CommitMessage)
+// return nil
+// },
+// }
+//
+// consumer.RegisterEventListener(listener)
+// err := consumer.Subscribe("TopicTest")
+// if err != nil {
+// panic(err)
+// }
+//
+// time.Sleep(100 * time.Second)
+//}
+
+func GetFullMessage() *primitive.Message {
+ event := ce.NewEvent()
+ event.SetSubject("TopicTest")
+ event.SetID("MockEventId")
+ event.SetDataContentType("application/json")
+ event.SetExtension("protocoltype", "http")
+ event.SetData("application/json", "{\"data\":\"foo\"}")
+ message, _ := convert.NewRocketMQMessageWriter("test-topic").ToMessage(context.Background(), &event)
+ return message
+}
diff --git a/plugin/connector/rocketmq/convert/message_ext_convert.go b/plugin/connector/rocketmq/convert/message_ext_convert.go
new file mode 100644
index 0000000..0b3d9c0
--- /dev/null
+++ b/plugin/connector/rocketmq/convert/message_ext_convert.go
@@ -0,0 +1,33 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package convert
+
+import (
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/constants"
+ "github.com/apache/rocketmq-client-go/v2/primitive"
+ "strings"
+)
+
+// TransferMessageSystemProperties re-set message system properties to fix pattern
+func TransferMessageSystemProperties(message *primitive.Message) {
+ for propertyKey := range constants.RocketMQMessageProperties.Iter() {
+ if val := message.GetProperty(propertyKey); len(val) != 0 {
+ key := strings.ReplaceAll(strings.ToLower(propertyKey), "_", constants.MessagePropertySeparator)
+ message.WithProperty(key, val)
+ message.RemoveProperty(propertyKey)
+ }
+ }
+}
diff --git a/plugin/connector/rocketmq/convert/rocketmq_message_reader.go b/plugin/connector/rocketmq/convert/rocketmq_message_reader.go
new file mode 100644
index 0000000..8d22baa
--- /dev/null
+++ b/plugin/connector/rocketmq/convert/rocketmq_message_reader.go
@@ -0,0 +1,110 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package convert
+
+import (
+ "bytes"
+ "context"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/constants"
+ "github.com/apache/rocketmq-client-go/v2/primitive"
+ ce "github.com/cloudevents/sdk-go/v2"
+ "github.com/cloudevents/sdk-go/v2/binding"
+ "github.com/cloudevents/sdk-go/v2/binding/spec"
+ "github.com/cloudevents/sdk-go/v2/event"
+)
+
+// RocketMQMessageReader cloud event message reader
+type RocketMQMessageReader struct {
+ topic string
+ properties map[string]string
+ body []byte
+ version spec.Version
+}
+
+// NewRocketMQMessageReader get RocketMQ message reader
+func NewRocketMQMessageReader(message *primitive.Message) *RocketMQMessageReader {
+ return &RocketMQMessageReader{
+ topic: message.Topic,
+ properties: message.GetProperties(),
+ body: message.Body,
+ version: spec.VS.Version(event.CloudEventsVersionV1),
+ }
+}
+
+// ReadEncoding the RocketMQMessageReader only supports binary encoding
+func (r *RocketMQMessageReader) ReadEncoding() binding.Encoding {
+ return binding.EncodingBinary
+}
+
+// ReadBinary read message and write through encoder
+func (r *RocketMQMessageReader) ReadBinary(ctx context.Context, encoder binding.BinaryWriter) (err error) {
+ subject := r.version.Attribute("subject")
+ encoder.SetAttribute(subject, r.topic)
+
+ msgType := r.version.Attribute("type")
+ encoder.SetAttribute(msgType, constants.CloudEventMessageType)
+
+ contentType := r.version.Attribute("datacontenttype")
+ encoder.SetAttribute(contentType, r.properties[constants.RocketMQMessageContentTypeProperties])
+
+ for k, v := range r.properties {
+ attr := r.version.Attribute(k)
+ if attr != nil {
+ err = encoder.SetAttribute(attr, v)
+ } else {
+ err = encoder.SetExtension(k, v)
+ }
+ if err != nil {
+ return err
+ }
+ }
+
+ if r.body != nil {
+ err = encoder.SetData(bytes.NewReader(r.body))
+ if err != nil {
+ return err
+ }
+ }
+ return
+}
+
+// GetAttribute get attribute
+func (r *RocketMQMessageReader) GetAttribute(k spec.Kind) (spec.Attribute, interface{}) {
+ attr := r.version.Attribute(k.String())
+ if attr != nil {
+ return attr, r.properties[k.String()]
+ }
+ return attr, nil
+}
+
+// GetExtension get extension
+func (r *RocketMQMessageReader) GetExtension(s string) interface{} {
+ return r.properties[s]
+}
+
+// ReadStructured the RocketMQMessageReader only supports binary encoding
+func (r *RocketMQMessageReader) ReadStructured(context.Context, binding.StructuredWriter) error {
+ return binding.ErrNotStructured
+}
+
+// ToCloudEvent convert RocketMQ message to cloud event
+func (r *RocketMQMessageReader) ToCloudEvent(ctx context.Context) (*ce.Event, error) {
+ event, err := binding.ToEvent(ctx, r)
+ if err != nil {
+ return nil, err
+ }
+ return event, nil
+}
diff --git a/plugin/connector/rocketmq/convert/rocketmq_message_reader_test.go b/plugin/connector/rocketmq/convert/rocketmq_message_reader_test.go
new file mode 100644
index 0000000..3ac3946
--- /dev/null
+++ b/plugin/connector/rocketmq/convert/rocketmq_message_reader_test.go
@@ -0,0 +1,47 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package convert
+
+import (
+ "context"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/constants"
+ "github.com/apache/rocketmq-client-go/v2/primitive"
+ "github.com/cloudevents/sdk-go/v2/event"
+ "github.com/cloudevents/sdk-go/v2/event/datacodec"
+ "github.com/stretchr/testify/require"
+ "testing"
+)
+
+func TestRocketMQMessageReader_ToCloudEvent(t *testing.T) {
+ e, err := NewRocketMQMessageReader(getTestMessage()).ToCloudEvent(context.Background())
+ require.True(t, err == nil)
+ require.True(t, e.ID() == "MockEventId")
+ require.True(t, e.Extensions()["protocoltype"] == "http")
+ data, _ := datacodec.Encode(context.Background(), event.ApplicationJSON, "{\"data\":\"foo\"}")
+ require.True(t, string(e.Data()) == string(data))
+}
+
+func getTestMessage() *primitive.Message {
+ data, _ := datacodec.Encode(context.Background(), event.ApplicationJSON, "{\"data\":\"foo\"}")
+ message := &primitive.Message{
+ Topic: "TopicTest",
+ Body: data,
+ }
+ message.WithProperty("id", "MockEventId")
+ message.WithProperty(constants.RocketMQMessageContentTypeProperties, event.ApplicationJSON)
+ message.WithProperty("protocoltype", "http")
+ return message
+}
diff --git a/plugin/connector/rocketmq/convert/rocketmq_message_writer.go b/plugin/connector/rocketmq/convert/rocketmq_message_writer.go
new file mode 100644
index 0000000..d6185ff
--- /dev/null
+++ b/plugin/connector/rocketmq/convert/rocketmq_message_writer.go
@@ -0,0 +1,86 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package convert
+
+import (
+ "context"
+ "github.com/apache/rocketmq-client-go/v2/primitive"
+ ce "github.com/cloudevents/sdk-go/v2"
+ "github.com/cloudevents/sdk-go/v2/binding"
+ "github.com/cloudevents/sdk-go/v2/binding/spec"
+ "github.com/cloudevents/sdk-go/v2/types"
+ "io"
+ "io/ioutil"
+)
+
+// RocketMQMessageWriter cloud event message writer
+type RocketMQMessageWriter struct {
+ message *primitive.Message
+}
+
+// NewRocketMQMessageWriter get RocketMQ message writer
+func NewRocketMQMessageWriter(topic string) *RocketMQMessageWriter {
+ message := &primitive.Message{}
+ message.Topic = topic
+ return &RocketMQMessageWriter{message: message}
+}
+
+// SetAttribute set attribute
+func (r *RocketMQMessageWriter) SetAttribute(attribute spec.Attribute, value interface{}) error {
+ val, err := types.Format(value)
+ if err != nil {
+ return err
+ }
+ r.message.WithProperty(attribute.Name(), val)
+ return nil
+}
+
+// SetExtension set extension
+func (r *RocketMQMessageWriter) SetExtension(name string, value interface{}) error {
+ r.message.WithProperty(name, value.(string))
+ return nil
+}
+
+// Start to start writing, do nothing
+func (r *RocketMQMessageWriter) Start(ctx context.Context) error {
+ // No-Op
+ return nil
+}
+
+// SetData set data from reader
+func (r *RocketMQMessageWriter) SetData(data io.Reader) error {
+ b, err := ioutil.ReadAll(data)
+ if err != nil {
+ return nil
+ }
+ r.message.Body = b
+ return nil
+}
+
+// End the end of writing, do nothing
+func (r *RocketMQMessageWriter) End(ctx context.Context) error {
+ // No-Op
+ return nil
+}
+
+// ToMessage convert cloud event to RocketMQ message
+func (r *RocketMQMessageWriter) ToMessage(ctx context.Context, cloudEvent *ce.Event) (*primitive.Message, error) {
+ _, err := binding.Write(binding.WithForceBinary(ctx), (*binding.EventMessage)(cloudEvent), nil, r)
+ if err != nil {
+ return nil, err
+ }
+ return r.message, err
+}
diff --git a/plugin/connector/rocketmq/convert/rocketmq_message_writer_test.go b/plugin/connector/rocketmq/convert/rocketmq_message_writer_test.go
new file mode 100644
index 0000000..6835b26
--- /dev/null
+++ b/plugin/connector/rocketmq/convert/rocketmq_message_writer_test.go
@@ -0,0 +1,44 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package convert
+
+import (
+ "context"
+ ce "github.com/cloudevents/sdk-go/v2"
+ "github.com/cloudevents/sdk-go/v2/event"
+ "github.com/cloudevents/sdk-go/v2/event/datacodec"
+ "github.com/stretchr/testify/require"
+ "testing"
+)
+
+func TestWriteBinary(t *testing.T) {
+ message, err := NewRocketMQMessageWriter("test-topic").ToMessage(context.Background(), getTestEvent())
+ require.True(t, err == nil)
+ require.True(t, message.GetProperty("id") == "MockEventId")
+ require.True(t, message.GetProperty("protocoltype") == "http")
+ data, _ := datacodec.Encode(context.Background(), event.ApplicationJSON, "{\"data\":\"foo\"}")
+ require.True(t, string(message.Body) == string(data))
+}
+
+func getTestEvent() *ce.Event {
+ event := ce.NewEvent()
+ event.SetSubject("TopicTest")
+ event.SetID("MockEventId")
+ event.SetDataContentType("application/json")
+ event.SetExtension("protocoltype", "http")
+ event.SetData("application/json", "{\"data\":\"foo\"}")
+ return &event
+}
diff --git a/plugin/connector/rocketmq/factory.go b/plugin/connector/rocketmq/factory.go
new file mode 100644
index 0000000..78de461
--- /dev/null
+++ b/plugin/connector/rocketmq/factory.go
@@ -0,0 +1,84 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package rocketmq
+
+import (
+ "errors"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector"
+)
+
+func init() {
+ plugin.Register("rocketmq", &Factory{})
+}
+
+type Factory struct {
+ plugin.Plugin
+ properties map[string]string
+ producer connector.Producer
+ consumer connector.Consumer
+}
+
+func (f *Factory) Type() string {
+ return connector.PluginType
+}
+
+func (f *Factory) Setup(name string, dec plugin.Decoder) error {
+ if dec == nil {
+ return errors.New(" producer config decoder empty")
+ }
+ properties := make(map[string]string)
+ if err := dec.Decode(properties); err != nil {
+ return err
+ }
+ f.properties = properties
+
+ consumer := NewConsumer()
+ err := consumer.InitConsumer(f.properties)
+ if err != nil {
+ return err
+ }
+ err = consumer.Start()
+ if err != nil {
+ return err
+ }
+ f.consumer = consumer
+
+ producer := NewProducer()
+ err = producer.InitProducer(f.properties)
+ if err != nil {
+ return err
+ }
+ err = producer.Start()
+ if err != nil {
+ return err
+ }
+ f.producer = producer
+
+ return nil
+}
+
+func (f *Factory) GetProducer() (connector.Producer, error) {
+ return f.producer, nil
+}
+
+func (f *Factory) GetConsumer() (connector.Consumer, error) {
+ return f.consumer, nil
+}
+
+func (f *Factory) GetResource() (connector.Resource, error) {
+ return &Resource{}, nil
+}
diff --git a/plugin/connector/rocketmq/mock/mock_rocketmq_consumer.go b/plugin/connector/rocketmq/mock/mock_rocketmq_consumer.go
new file mode 100644
index 0000000..0e464cc
--- /dev/null
+++ b/plugin/connector/rocketmq/mock/mock_rocketmq_consumer.go
@@ -0,0 +1,145 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// Code generated by MockGen. DO NOT EDIT.
+// Source: /Users/chenzhou/horoc/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/client/rocketmq_consumer.go
+
+// Package mock is a generated GoMock package.
+package mock
+
+import (
+ reflect "reflect"
+
+ client "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/client"
+ consumer "github.com/apache/rocketmq-client-go/v2/consumer"
+ gomock "github.com/golang/mock/gomock"
+)
+
+// MockRocketMQConsumer is a mock of RocketMQConsumer interface.
+type MockRocketMQConsumer struct {
+ ctrl *gomock.Controller
+ recorder *MockRocketMQConsumerMockRecorder
+}
+
+// MockRocketMQConsumerMockRecorder is the mock recorder for MockRocketMQConsumer.
+type MockRocketMQConsumerMockRecorder struct {
+ mock *MockRocketMQConsumer
+}
+
+// NewMockRocketMQConsumer creates a new mock instance.
+func NewMockRocketMQConsumer(ctrl *gomock.Controller) *MockRocketMQConsumer {
+ mock := &MockRocketMQConsumer{ctrl: ctrl}
+ mock.recorder = &MockRocketMQConsumerMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockRocketMQConsumer) EXPECT() *MockRocketMQConsumerMockRecorder {
+ return m.recorder
+}
+
+// IsBroadCasting mocks base method.
+func (m *MockRocketMQConsumer) IsBroadCasting() bool {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "IsBroadCasting")
+ ret0, _ := ret[0].(bool)
+ return ret0
+}
+
+// IsBroadCasting indicates an expected call of IsBroadCasting.
+func (mr *MockRocketMQConsumerMockRecorder) IsBroadCasting() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsBroadCasting", reflect.TypeOf((*MockRocketMQConsumer)(nil).IsBroadCasting))
+}
+
+// Resume mocks base method.
+func (m *MockRocketMQConsumer) Resume() {
+ m.ctrl.T.Helper()
+ m.ctrl.Call(m, "Resume")
+}
+
+// Resume indicates an expected call of Resume.
+func (mr *MockRocketMQConsumerMockRecorder) Resume() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Resume", reflect.TypeOf((*MockRocketMQConsumer)(nil).Resume))
+}
+
+// Shutdown mocks base method.
+func (m *MockRocketMQConsumer) Shutdown() error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Shutdown")
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Shutdown indicates an expected call of Shutdown.
+func (mr *MockRocketMQConsumerMockRecorder) Shutdown() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockRocketMQConsumer)(nil).Shutdown))
+}
+
+// Start mocks base method.
+func (m *MockRocketMQConsumer) Start() error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Start")
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Start indicates an expected call of Start.
+func (mr *MockRocketMQConsumerMockRecorder) Start() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockRocketMQConsumer)(nil).Start))
+}
+
+// Subscribe mocks base method.
+func (m *MockRocketMQConsumer) Subscribe(topic string, selector consumer.MessageSelector, f client.SubscribeFunc) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Subscribe", topic, selector, f)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Subscribe indicates an expected call of Subscribe.
+func (mr *MockRocketMQConsumerMockRecorder) Subscribe(topic, selector, f interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockRocketMQConsumer)(nil).Subscribe), topic, selector, f)
+}
+
+// Suspend mocks base method.
+func (m *MockRocketMQConsumer) Suspend() {
+ m.ctrl.T.Helper()
+ m.ctrl.Call(m, "Suspend")
+}
+
+// Suspend indicates an expected call of Suspend.
+func (mr *MockRocketMQConsumerMockRecorder) Suspend() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Suspend", reflect.TypeOf((*MockRocketMQConsumer)(nil).Suspend))
+}
+
+// Unsubscribe mocks base method.
+func (m *MockRocketMQConsumer) Unsubscribe(topic string) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Unsubscribe", topic)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Unsubscribe indicates an expected call of Unsubscribe.
+func (mr *MockRocketMQConsumerMockRecorder) Unsubscribe(topic interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unsubscribe", reflect.TypeOf((*MockRocketMQConsumer)(nil).Unsubscribe), topic)
+}
diff --git a/plugin/connector/rocketmq/mock/mock_rocketmq_producer.go b/plugin/connector/rocketmq/mock/mock_rocketmq_producer.go
new file mode 100644
index 0000000..c8b2805
--- /dev/null
+++ b/plugin/connector/rocketmq/mock/mock_rocketmq_producer.go
@@ -0,0 +1,167 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// Code generated by MockGen. DO NOT EDIT.
+// Source: /Users/chenzhou/horoc/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/producer/rocketmq_producer.go
+
+// Package mock is a generated GoMock package.
+package mock
+
+import (
+ context "context"
+ reflect "reflect"
+ time "time"
+
+ primitive "github.com/apache/rocketmq-client-go/v2/primitive"
+ gomock "github.com/golang/mock/gomock"
+)
+
+// MockRocketMQProducer is a mock of RocketMQProducer interface.
+type MockRocketMQProducer struct {
+ ctrl *gomock.Controller
+ recorder *MockRocketMQProducerMockRecorder
+}
+
+// MockRocketMQProducerMockRecorder is the mock recorder for MockRocketMQProducer.
+type MockRocketMQProducerMockRecorder struct {
+ mock *MockRocketMQProducer
+}
+
+// NewMockRocketMQProducer creates a new mock instance.
+func NewMockRocketMQProducer(ctrl *gomock.Controller) *MockRocketMQProducer {
+ mock := &MockRocketMQProducer{ctrl: ctrl}
+ mock.recorder = &MockRocketMQProducerMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockRocketMQProducer) EXPECT() *MockRocketMQProducerMockRecorder {
+ return m.recorder
+}
+
+// Request mocks base method.
+func (m *MockRocketMQProducer) Request(ctx context.Context, ttl time.Duration, msg *primitive.Message) (*primitive.Message, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Request", ctx, ttl, msg)
+ ret0, _ := ret[0].(*primitive.Message)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// Request indicates an expected call of Request.
+func (mr *MockRocketMQProducerMockRecorder) Request(ctx, ttl, msg interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Request", reflect.TypeOf((*MockRocketMQProducer)(nil).Request), ctx, ttl, msg)
+}
+
+// RequestAsync mocks base method.
+func (m *MockRocketMQProducer) RequestAsync(ctx context.Context, ttl time.Duration, callback func(context.Context, *primitive.Message, error), msg *primitive.Message) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "RequestAsync", ctx, ttl, callback, msg)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// RequestAsync indicates an expected call of RequestAsync.
+func (mr *MockRocketMQProducerMockRecorder) RequestAsync(ctx, ttl, callback, msg interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestAsync", reflect.TypeOf((*MockRocketMQProducer)(nil).RequestAsync), ctx, ttl, callback, msg)
+}
+
+// SendAsync mocks base method.
+func (m *MockRocketMQProducer) SendAsync(ctx context.Context, mq func(context.Context, *primitive.SendResult, error), msg ...*primitive.Message) error {
+ m.ctrl.T.Helper()
+ varargs := []interface{}{ctx, mq}
+ for _, a := range msg {
+ varargs = append(varargs, a)
+ }
+ ret := m.ctrl.Call(m, "SendAsync", varargs...)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// SendAsync indicates an expected call of SendAsync.
+func (mr *MockRocketMQProducerMockRecorder) SendAsync(ctx, mq interface{}, msg ...interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ varargs := append([]interface{}{ctx, mq}, msg...)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendAsync", reflect.TypeOf((*MockRocketMQProducer)(nil).SendAsync), varargs...)
+}
+
+// SendOneWay mocks base method.
+func (m *MockRocketMQProducer) SendOneWay(ctx context.Context, mq ...*primitive.Message) error {
+ m.ctrl.T.Helper()
+ varargs := []interface{}{ctx}
+ for _, a := range mq {
+ varargs = append(varargs, a)
+ }
+ ret := m.ctrl.Call(m, "SendOneWay", varargs...)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// SendOneWay indicates an expected call of SendOneWay.
+func (mr *MockRocketMQProducerMockRecorder) SendOneWay(ctx interface{}, mq ...interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ varargs := append([]interface{}{ctx}, mq...)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendOneWay", reflect.TypeOf((*MockRocketMQProducer)(nil).SendOneWay), varargs...)
+}
+
+// SendSync mocks base method.
+func (m *MockRocketMQProducer) SendSync(ctx context.Context, mq ...*primitive.Message) (*primitive.SendResult, error) {
+ m.ctrl.T.Helper()
+ varargs := []interface{}{ctx}
+ for _, a := range mq {
+ varargs = append(varargs, a)
+ }
+ ret := m.ctrl.Call(m, "SendSync", varargs...)
+ ret0, _ := ret[0].(*primitive.SendResult)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SendSync indicates an expected call of SendSync.
+func (mr *MockRocketMQProducerMockRecorder) SendSync(ctx interface{}, mq ...interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ varargs := append([]interface{}{ctx}, mq...)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendSync", reflect.TypeOf((*MockRocketMQProducer)(nil).SendSync), varargs...)
+}
+
+// Shutdown mocks base method.
+func (m *MockRocketMQProducer) Shutdown() error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Shutdown")
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Shutdown indicates an expected call of Shutdown.
+func (mr *MockRocketMQProducerMockRecorder) Shutdown() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockRocketMQProducer)(nil).Shutdown))
+}
+
+// Start mocks base method.
+func (m *MockRocketMQProducer) Start() error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Start")
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Start indicates an expected call of Start.
+func (mr *MockRocketMQProducerMockRecorder) Start() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockRocketMQProducer)(nil).Start))
+}
diff --git a/plugin/connector/rocketmq/producer.go b/plugin/connector/rocketmq/producer.go
new file mode 100644
index 0000000..49f7ca7
--- /dev/null
+++ b/plugin/connector/rocketmq/producer.go
@@ -0,0 +1,236 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package rocketmq
+
+import (
+ "context"
+ "errors"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/client"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/constants"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/convert"
+ "github.com/apache/rocketmq-client-go/v2/primitive"
+ ce "github.com/cloudevents/sdk-go/v2"
+ "strings"
+ "sync"
+ "time"
+)
+
+type SendCallback func(ctx context.Context, result *primitive.SendResult, err error)
+
+type Producer struct {
+ rocketMQProducer client.RocketMQProducer
+ started bool
+ mutex sync.Mutex
+}
+
+// NewProducer get new producer, needs to be Initiated before using
+func NewProducer() *Producer {
+ return &Producer{}
+}
+
+// InitProducer init producer by properties
+func (p *Producer) InitProducer(properties map[string]string) error {
+ producer, err := client.NewRocketMQProducerWrapper(properties)
+ if err != nil {
+ return err
+ }
+ p.rocketMQProducer = producer
+ return nil
+}
+
+// IsStarted check if producer is started
+func (p *Producer) IsStarted() bool {
+ return p.rocketMQProducer != nil && p.started
+}
+
+// IsClosed check if producer is closed
+func (p *Producer) IsClosed() bool {
+ return p.rocketMQProducer != nil && !p.started
+}
+
+// Start make producer started
+func (p *Producer) Start() error {
+ if p.rocketMQProducer == nil {
+ return errors.New("start rocketmq producer fail, producer should be initiated first")
+ }
+ p.mutex.Lock()
+ defer p.mutex.Unlock()
+ if !p.started {
+ err := p.rocketMQProducer.Start()
+ if err != nil {
+ return err
+ }
+ p.started = true
+ }
+ return nil
+}
+
+// Shutdown terminate the producer
+func (p *Producer) Shutdown() error {
+ if p.rocketMQProducer == nil {
+ return errors.New("shutdown rocketmq producer fail, producer should be initiated first")
+ }
+ p.mutex.Lock()
+ defer p.mutex.Unlock()
+ if p.started {
+ err := p.rocketMQProducer.Shutdown()
+ if err != nil {
+ return err
+ }
+ p.started = false
+ }
+ return nil
+}
+
+// Publish async publish message to broker
+func (p *Producer) Publish(ctx context.Context, event *ce.Event, callback *connector.SendCallback) error {
+ if err := p.checkProducerStatus(); err != nil {
+ return err
+ }
+ msg, err := convert.NewRocketMQMessageWriter(event.Subject()).ToMessage(ctx, event)
+ if err != nil {
+ return err
+ }
+ p.supplySysProp(msg, event)
+ err = p.rocketMQProducer.SendAsync(ctx, p.sendCallbackConvert(callback), msg)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// SendOneway async send message without callback
+func (p *Producer) SendOneway(ctx context.Context, event *ce.Event) error {
+ if err := p.checkProducerStatus(); err != nil {
+ return err
+ }
+ msg, err := convert.NewRocketMQMessageWriter(event.Subject()).ToMessage(ctx, event)
+ if err != nil {
+ return err
+ }
+ p.supplySysProp(msg, event)
+ err = p.rocketMQProducer.SendOneWay(ctx, msg)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// Request async request message
+func (p *Producer) Request(ctx context.Context, event *ce.Event, callback *connector.RequestReplyCallback,
+ timeout time.Duration) error {
+ if err := p.checkProducerStatus(); err != nil {
+ return err
+ }
+ msg, err := convert.NewRocketMQMessageWriter(event.Subject()).ToMessage(ctx, event)
+ if err != nil {
+ return err
+ }
+ p.supplySysProp(msg, event)
+ return p.rocketMQProducer.RequestAsync(ctx, timeout, p.requestReplyCallbackConvert(event.Subject(), callback), msg)
+}
+
+// Reply async send message to reply
+func (p *Producer) Reply(ctx context.Context, event *ce.Event, callback *connector.SendCallback) error {
+ if err := p.checkProducerStatus(); err != nil {
+ return err
+ }
+ msg, err := convert.NewRocketMQMessageWriter(event.Subject()).ToMessage(ctx, event)
+ if err != nil {
+ return err
+ }
+ p.supplySysProp(msg, event)
+ err = p.rocketMQProducer.SendAsync(ctx, p.sendCallbackConvert(callback))
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// CheckTopicExist RocketMQ go-sdk doesn't support topic check
+func (p *Producer) CheckTopicExist(topicName string) (bool, error) {
+ // RocketMQ go-sdk doesn't support topic check
+ return false, errors.New("rocketmq producer doesn't support topic check")
+}
+
+// SetExtFields do nothing, RocketMQ go-sdk doesn't support dynamic client option modify
+func (p *Producer) SetExtFields() error {
+ return nil
+}
+
+// checkProducerStatus check is the producer has started
+func (p *Producer) checkProducerStatus() error {
+ if p.rocketMQProducer == nil {
+ return errors.New("rocketmq producer has not been initiated")
+ }
+
+ if !p.started {
+ return errors.New("rocketmq producer has not started")
+ }
+ return nil
+}
+
+// supplySysProp convert producer client's system properties format
+func (p *Producer) supplySysProp(message *primitive.Message, cloudEvent *ce.Event) {
+ for _, propertyKey := range constants.RocketMQMessageProperties.ToSlice() {
+ key := strings.ReplaceAll(strings.ToLower(propertyKey), "_", constants.MessagePropertySeparator)
+ if val, ok := cloudEvent.Extensions()[key]; ok {
+ message.WithProperty(propertyKey, val.(string))
+ message.RemoveProperty(key)
+ }
+ }
+}
+
+// sendCallbackConvert convert connector API callback to RocketMQ callback
+func (p *Producer) sendCallbackConvert(callback *connector.SendCallback) SendCallback {
+ return func(ctx context.Context, result *primitive.SendResult, err error) {
+ if err != nil {
+ callback.OnError(&connector.ErrorResult{
+ Topic: result.MessageQueue.Topic,
+ Err: err,
+ })
+ }
+ callback.OnSuccess(p.convertToSendResult(result))
+ }
+}
+
+// requestReplyCallbackConvert convert connector API callback to RocketMQ callback
+func (p *Producer) requestReplyCallbackConvert(topic string,
+ callback *connector.RequestReplyCallback) func(ctx context.Context, msg *primitive.Message, err error) {
+ return func(ctx context.Context, msg *primitive.Message, err error) {
+ if err != nil {
+ callback.OnError(&connector.ErrorResult{
+ Topic: topic,
+ Err: err,
+ })
+ return
+ }
+ convert.TransferMessageSystemProperties(msg)
+ event, err := convert.NewRocketMQMessageReader(msg).ToCloudEvent(ctx)
+ if err != nil {
+ panic(err)
+ }
+ callback.OnSuccess(event)
+ }
+}
+
+func (p *Producer) convertToSendResult(result *primitive.SendResult) *connector.SendResult {
+ return &connector.SendResult{
+ MessageId: result.MsgID,
+ Topic: result.MessageQueue.Topic,
+ }
+}
diff --git a/plugin/connector/rocketmq/producer_test.go b/plugin/connector/rocketmq/producer_test.go
new file mode 100644
index 0000000..576fe41
--- /dev/null
+++ b/plugin/connector/rocketmq/producer_test.go
@@ -0,0 +1,214 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package rocketmq
+
+import (
+ "context"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector/rocketmq/mock"
+ "github.com/apache/rocketmq-client-go/v2/primitive"
+ ce "github.com/cloudevents/sdk-go/v2"
+ "github.com/cloudevents/sdk-go/v2/event"
+ "github.com/cloudevents/sdk-go/v2/event/datacodec"
+ "github.com/golang/mock/gomock"
+ "github.com/stretchr/testify/require"
+ "testing"
+ "time"
+)
+
+func GetProducerWithMockClient(ctl *gomock.Controller) *Producer {
+ producer := NewProducer()
+ producer.InitProducer(make(map[string]string))
+ mockRocketMQProducer := mock.NewMockRocketMQProducer(ctl)
+ mockRocketMQProducer.EXPECT().Start().Return(nil)
+ mockRocketMQProducer.EXPECT().Shutdown().Return(nil)
+ producer.rocketMQProducer = mockRocketMQProducer
+ return producer
+}
+
+func TestProducer_Start(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+ producer := GetProducerWithMockClient(ctrl)
+ producer.Start()
+ require.True(t, producer.IsStarted())
+ producer.Shutdown()
+}
+
+func TestProducer_Shutdown(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+ producer := GetProducerWithMockClient(ctrl)
+ producer.Start()
+ producer.Shutdown()
+ require.False(t, producer.IsStarted())
+}
+
+func TestProducer_Publish(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+ producer := GetProducerWithMockClient(ctrl)
+ producer.Start()
+
+ callback := &connector.SendCallback{
+ OnSuccess: func(result *connector.SendResult) {
+ require.True(t, result.Topic == "TopicTest")
+ require.True(t, result.MessageId == "MockMsgID")
+ },
+ OnError: func(result *connector.ErrorResult) {
+ panic(result.Err)
+ },
+ }
+
+ producer.rocketMQProducer.(*mock.MockRocketMQProducer).EXPECT().
+ SendAsync(gomock.Any(), gomock.Any(), gomock.Any()).Do(
+ func(ctx, mq interface{}, msg ...interface{}) {
+ result := &primitive.SendResult{
+ Status: primitive.SendOK,
+ MsgID: "MockMsgID",
+ MessageQueue: &primitive.MessageQueue{
+ Topic: "TopicTest",
+ },
+ }
+ mq.(func(ctx context.Context, result *primitive.SendResult, err error))(context.Background(), result, nil)
+ },
+ )
+ err := producer.Publish(context.Background(), GetFullEvent(), callback)
+ require.True(t, err == nil)
+ producer.Shutdown()
+}
+
+func TestProducer_SendOneway(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+ producer := GetProducerWithMockClient(ctrl)
+ producer.Start()
+
+ producer.rocketMQProducer.(*mock.MockRocketMQProducer).EXPECT().
+ SendOneWay(gomock.Any(), gomock.Any()).Do(
+ func(ctx context.Context, mq ...*primitive.Message) {
+ encodedData, _ := datacodec.Encode(context.Background(), event.ApplicationJSON, "{\"data\":\"foo\"}")
+ require.True(t, len(mq) == 1)
+ require.True(t, mq[0].Topic == "TopicTest")
+ require.True(t, string(mq[0].Body) == string(encodedData))
+ },
+ )
+ err := producer.SendOneway(context.Background(), GetFullEvent())
+ require.True(t, err == nil)
+ producer.Shutdown()
+}
+
+func TestProducer_Request(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+ producer := GetProducerWithMockClient(ctrl)
+ producer.Start()
+
+ callback := &connector.RequestReplyCallback{
+ OnSuccess: func(event *ce.Event) {
+ require.True(t, event.Subject() == "TopicTest")
+ require.True(t, string(event.Data()) == "{\"data\":\"resp\"}")
+ },
+ OnError: func(result *connector.ErrorResult) {
+ panic(result.Err)
+ },
+ }
+
+ producer.rocketMQProducer.(*mock.MockRocketMQProducer).EXPECT().
+ RequestAsync(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Do(
+ func(ctx context.Context, ttl time.Duration,
+ callback func(ctx context.Context, msg *primitive.Message, err error),
+ msg *primitive.Message) {
+ responseMessage := &primitive.Message{
+ Topic: "TopicTest",
+ Body: []byte("{\"data\":\"resp\"}"),
+ }
+ callback(context.Background(), responseMessage, nil)
+ },
+ )
+ err := producer.Request(context.Background(), GetFullEvent(), callback, time.Second)
+ require.True(t, err == nil)
+ producer.Shutdown()
+}
+
+func TestProducer_Reply(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ defer ctrl.Finish()
+ producer := GetProducerWithMockClient(ctrl)
+ producer.Start()
+
+ callback := &connector.SendCallback{
+ OnSuccess: func(result *connector.SendResult) {
+ require.True(t, result.Topic == "TopicTest")
+ require.True(t, result.MessageId == "MockMsgID")
+ },
+ OnError: func(result *connector.ErrorResult) {
+ panic(result.Err)
+ },
+ }
+ producer.rocketMQProducer.(*mock.MockRocketMQProducer).EXPECT().
+ SendAsync(gomock.Any(), gomock.Any(), gomock.Any()).Do(
+ func(ctx, mq interface{}, msg ...interface{}) {
+ result := &primitive.SendResult{
+ Status: primitive.SendOK,
+ MsgID: "MockMsgID",
+ MessageQueue: &primitive.MessageQueue{
+ Topic: "TopicTest",
+ },
+ }
+ mq.(func(ctx context.Context, result *primitive.SendResult, err error))(context.Background(), result, nil)
+ },
+ )
+ err := producer.Reply(context.Background(), GetFullEvent(), callback)
+ require.True(t, err == nil)
+ producer.Shutdown()
+}
+
+func GetFullEvent() *ce.Event {
+ event := ce.NewEvent()
+ event.SetSubject("TopicTest")
+ event.SetID("MockEventId")
+ event.SetDataContentType("application/json")
+ event.SetExtension("protocoltype", "http")
+ event.SetData("application/json", "{\"data\":\"foo\"}")
+ return &event
+}
+
+//func TestProducer_Publish_Broker(t *testing.T) {
+// properties := make(map[string]string)
+// properties["access_points"] = "127.0.0.1:9876"
+// producer := NewProducer()
+// producer.InitProducer(properties)
+// producer.Start()
+//
+// var wg sync.WaitGroup
+// wg.Add(1)
+// callback := &connector.SendCallback{
+// OnSuccess: func(result *connector.SendResult) {
+// require.True(t, result.Topic == "TopicTest")
+// wg.Done()
+// },
+// OnError: func(result *connector.ErrorResult) {
+// panic(result.Err)
+// },
+// }
+//
+// err := producer.Publish(context.Background(), GetFullEvent(), callback)
+// require.True(t, err == nil)
+//
+// wg.Wait()
+// producer.Shutdown()
+//}
diff --git a/plugin/connector/rocketmq/resource.go b/plugin/connector/rocketmq/resource.go
new file mode 100644
index 0000000..b9b39c0
--- /dev/null
+++ b/plugin/connector/rocketmq/resource.go
@@ -0,0 +1,29 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package rocketmq
+
+type Resource struct {
+}
+
+func (r Resource) Init() error {
+ // No-Op for RocketMQ resource
+ return nil
+}
+
+func (r Resource) Release() error {
+ // No-Op for RocketMQ resource
+ return nil
+}
diff --git a/plugin/connector/rocketmq/utils/oms_util.go b/plugin/connector/rocketmq/utils/oms_util.go
new file mode 100644
index 0000000..3d05c11
--- /dev/null
+++ b/plugin/connector/rocketmq/utils/oms_util.go
@@ -0,0 +1,27 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package utils
+
+import (
+ "fmt"
+ "os"
+ "time"
+)
+
+// GetInstanceName build instance name with pid and timestamp
+func GetInstanceName() string {
+ return fmt.Sprintf("%d_EventMesh_%d", os.Getegid(), time.Now().UnixNano())
+}
diff --git a/plugin/connector/standalone/broker.go b/plugin/connector/standalone/broker.go
new file mode 100644
index 0000000..b70f3ba
--- /dev/null
+++ b/plugin/connector/standalone/broker.go
@@ -0,0 +1,259 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package standalone
+
+import (
+ "errors"
+ "fmt"
+ "sync"
+ "time"
+
+ ce "github.com/cloudevents/sdk-go/v2"
+ "go.uber.org/atomic"
+)
+
+const (
+ defaultQueueSize = 1024
+ defaultExpireMills = 60 * 60 * 1000
+)
+
+// MessageQueue message storage of standalone broker
+type MessageQueue struct {
+ capacity int
+ items []*Message
+ mutex sync.Mutex
+ notFull sync.Cond
+ newMsg sync.Cond
+}
+
+func NewMessageQueue() (*MessageQueue, error) {
+ return NewMessageQueueWithCapacity(defaultQueueSize)
+}
+
+func NewMessageQueueWithCapacity(capacity int) (*MessageQueue, error) {
+ if capacity <= 0 {
+ return nil, errors.New("fail to create message queue: capacity must be positive")
+ }
+
+ queue := &MessageQueue{
+ capacity: capacity,
+ items: make([]*Message, 0, capacity),
+ }
+ queue.notFull = sync.Cond{L: &queue.mutex}
+ queue.newMsg = sync.Cond{L: &queue.mutex}
+ return queue, nil
+}
+
+func (q *MessageQueue) Put(message *Message) {
+ q.mutex.Lock()
+ defer q.mutex.Unlock()
+
+ // Wait util queue is not null
+ for q.capacity == len(q.items) {
+ q.notFull.Wait()
+ }
+ q.items = append(q.items, message)
+ q.newMsg.Signal()
+}
+
+// Get fetch message in top of queue, block if message queue is empty
+func (q *MessageQueue) Get() (*Message, error) {
+ q.mutex.Lock()
+ defer q.mutex.Unlock()
+
+ // Wait util queue is not empty
+ for len(q.items) == 0 {
+ q.newMsg.Wait()
+ }
+ return q.items[0], nil
+}
+
+// GetIfNotEmpty fetch message in top of queue, return nil if message queue is empty
+func (q *MessageQueue) GetIfNotEmpty() *Message {
+ q.mutex.Lock()
+ defer q.mutex.Unlock()
+
+ if len(q.items) > 0 {
+ return q.items[0]
+ }
+ return nil
+}
+
+// GetByOffset fetch message of curtain offset
+// return error if message has been deleted, block if message of that offset is not available currently
+func (q *MessageQueue) GetByOffset(offset int64) (*Message, error) {
+ q.mutex.Lock()
+ defer q.mutex.Unlock()
+
+ if offset < 0 {
+ return nil, fmt.Errorf("invalid offset param: %d", offset)
+ }
+
+ for {
+ // Wait util queue is not empty
+ if len(q.items) == 0 {
+ q.newMsg.Wait()
+ continue
+ }
+
+ lastMessage := q.items[len(q.items)-1]
+ // Wait util new message has been put
+ if lastMessage.GetOffset() < offset {
+ q.newMsg.Wait()
+ continue
+ }
+
+ firstMessage := q.items[0]
+ // message has been deleted
+ if firstMessage.GetOffset() > offset {
+ return nil, fmt.Errorf("offset has been deleted, offset : %d", offset)
+ }
+
+ index := int(offset - firstMessage.GetOffset())
+ return q.items[index], nil
+ }
+}
+
+// PopMessage remove message in the top, return nil if message queue is empty
+func (q *MessageQueue) PopMessage() *Message {
+ q.mutex.Lock()
+ defer q.mutex.Unlock()
+ if len(q.items) != 0 {
+ message := q.items[0]
+ q.items = q.items[1:]
+ return message
+ }
+ return nil
+}
+
+func (q *MessageQueue) Size() int {
+ return len(q.items)
+}
+
+// Broker a manager of different topic message queue
+type Broker struct {
+ // <topicName, messageQueue>
+ queueContainer map[string]*MessageQueue
+ // <topicName, offset>
+ offsetContainer map[string]*atomic.Int64
+ // <topicName, worker>
+ expireWorkers map[string]*MessageExpireWorker
+}
+
+var initOnce sync.Once
+var broker *Broker
+
+// GetBroker all topic shared same broker
+func GetBroker() *Broker {
+ initOnce.Do(func() {
+ broker = &Broker{
+ queueContainer: make(map[string]*MessageQueue),
+ offsetContainer: make(map[string]*atomic.Int64),
+ expireWorkers: make(map[string]*MessageExpireWorker),
+ }
+ },
+ )
+ return broker
+}
+
+func (b *Broker) TakeMessage(topicName string) (*Message, error) {
+ if err := b.CreateNewQueueIfAbsent(topicName); err != nil {
+ return nil, err
+ }
+ return b.queueContainer[topicName].Get()
+}
+
+func (b *Broker) TakeMessageByOffset(topicName string, offset int64) (*Message, error) {
+ if err := b.CreateNewQueueIfAbsent(topicName); err != nil {
+ return nil, err
+ }
+ return b.queueContainer[topicName].GetByOffset(offset)
+}
+
+func (b *Broker) PutMessage(topicName string, event *ce.Event) (message *Message, err error) {
+ if err = b.CreateNewQueueIfAbsent(topicName); err != nil {
+ return
+ }
+ message = &Message{
+ createTimeMills: time.Now().UnixMilli(),
+ event: event,
+ }
+ message.SetOffset(b.offsetContainer[topicName].Add(1))
+ b.queueContainer[topicName].Put(message)
+ return
+}
+
+func (b *Broker) CreateNewQueueIfAbsent(topicName string) (err error) {
+ if _, ok := b.queueContainer[topicName]; ok {
+ return
+ }
+ queue, err := NewMessageQueue()
+ if err != nil {
+ return
+ }
+
+ offset := atomic.NewInt64(0)
+ b.queueContainer[topicName] = queue
+ b.offsetContainer[topicName] = offset
+
+ expireWorker := &MessageExpireWorker{
+ messageQueue: queue,
+ quit: make(chan struct{}, 1),
+ expireMills: defaultExpireMills,
+ }
+ b.expireWorkers[topicName] = expireWorker
+ go expireWorker.schedule(5 * time.Second)
+ return
+}
+
+func (b *Broker) ExistTopic(topicName string) (exist bool) {
+ _, exist = b.queueContainer[topicName]
+ return
+}
+
+// MessageExpireWorker periodically evict expire message
+type MessageExpireWorker struct {
+ messageQueue *MessageQueue
+ quit chan struct{}
+ expireMills int64
+}
+
+func (w *MessageExpireWorker) schedule(interval time.Duration) {
+ ticket := time.NewTicker(interval)
+ for {
+ select {
+ case <-ticket.C:
+ w.doEvict()
+ case <-w.quit:
+ ticket.Stop()
+ return
+ }
+ }
+}
+
+func (w *MessageExpireWorker) doEvict() {
+ message := w.messageQueue.GetIfNotEmpty()
+ for message != nil {
+ if message.createTimeMills+w.expireMills < time.Now().UnixMilli() {
+ w.messageQueue.PopMessage()
+ }
+ message = w.messageQueue.GetIfNotEmpty()
+ }
+}
+
+func (w *MessageExpireWorker) stop() {
+ w.quit <- struct{}{}
+}
diff --git a/plugin/connector/standalone/consumer.go b/plugin/connector/standalone/consumer.go
new file mode 100644
index 0000000..2da2b03
--- /dev/null
+++ b/plugin/connector/standalone/consumer.go
@@ -0,0 +1,200 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package standalone
+
+import (
+ "context"
+ "fmt"
+ "sync"
+
+ ce "github.com/cloudevents/sdk-go/v2"
+ "github.com/pkg/errors"
+ "go.uber.org/atomic"
+
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/log"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector"
+)
+
+type Consumer struct {
+ broker *Broker
+ subscribes map[string]*SubscribeWorker
+ committedOffset map[string]*atomic.Int64
+ mutex sync.Mutex
+ listener connector.EventListener
+ started atomic.Bool
+}
+
+func NewConsumer() *Consumer {
+ return &Consumer{
+ broker: GetBroker(),
+ subscribes: make(map[string]*SubscribeWorker),
+ committedOffset: make(map[string]*atomic.Int64),
+ }
+}
+
+func (c *Consumer) Subscribe(topicName string) error {
+ c.mutex.Lock()
+ defer c.mutex.Unlock()
+
+ if c.IsClosed() {
+ return errors.New("fail to subscribe topic, consumer has been closed")
+ }
+
+ if _, ok := c.subscribes[topicName]; !ok {
+ err := c.broker.CreateNewQueueIfAbsent(topicName)
+ if err != nil {
+ return err
+ }
+ offset := atomic.NewInt64(0)
+ worker := &SubscribeWorker{
+ broker: broker,
+ topicName: topicName,
+ offset: offset,
+ listener: c.listener,
+ quit: make(chan struct{}, 1),
+ }
+
+ c.committedOffset[topicName] = offset
+ c.subscribes[topicName] = worker
+ go worker.run()
+ }
+ return nil
+}
+
+func (c *Consumer) Unsubscribe(topicName string) error {
+ c.mutex.Lock()
+ defer c.mutex.Unlock()
+ if worker, ok := c.subscribes[topicName]; ok {
+ delete(c.subscribes, topicName)
+ worker.Stop()
+ }
+ return nil
+}
+
+func (c *Consumer) UpdateOffset(ctx context.Context, events []*ce.Event) error {
+ c.mutex.Lock()
+ defer c.mutex.Unlock()
+ for _, event := range events {
+ topicName := event.Subject()
+ offset := GetOffsetFromEvent(event)
+ if curOffset, ok := c.committedOffset[topicName]; ok {
+ if offset <= 0 {
+ return fmt.Errorf("fail to update offset, invalid param, topic %s, offset : %d", topicName, offset)
+ }
+ if offset < curOffset.Load() {
+ return nil
+ }
+ curOffset.Store(offset)
+ }
+ }
+ return nil
+}
+
+func (c *Consumer) RegisterEventListener(listener *connector.EventListener) {
+ c.listener = *listener
+}
+
+func (c *Consumer) InitConsumer(properties map[string]string) error {
+ // No-Op for standalone connector
+ return nil
+}
+
+func (c *Consumer) Start() error {
+ c.started.CAS(false, true)
+ return nil
+}
+
+func (c *Consumer) Shutdown() error {
+ c.started.CAS(true, false)
+ if ok := c.started.CAS(true, false); ok {
+ for topicName, _ := range c.subscribes {
+ c.Unsubscribe(topicName)
+ delete(c.subscribes, topicName)
+ }
+ }
+ return nil
+}
+
+func (c *Consumer) IsStarted() bool {
+ return c.started.Load()
+}
+
+func (c *Consumer) IsClosed() bool {
+ return !c.started.Load()
+}
+
+// SubscribeWorker pollMessage from topic and manage consume offset
+type SubscribeWorker struct {
+ topicName string
+ broker *Broker
+ listener connector.EventListener
+ offset *atomic.Int64
+ quit chan struct{}
+}
+
+func (w *SubscribeWorker) run() {
+ for {
+ select {
+ case <-w.quit:
+ return
+ default:
+ err := w.pollMessage()
+ if err != nil {
+ // retry
+ log.Error("[Standalone Consumer] fail to poll message from broker, err=%v", err)
+ continue
+ }
+ }
+ }
+}
+
+func (w *SubscribeWorker) pollMessage() error {
+ var message *Message
+ var err error
+ if w.offset.Load() == 0 {
+ message, err = w.broker.TakeMessage(w.topicName)
+ if ok := w.offset.CAS(0, message.GetOffset()); !ok {
+ return nil
+ }
+ } else {
+ message, err = w.broker.TakeMessageByOffset(w.topicName, w.offset.Load()+1)
+ }
+ if err != nil {
+ return errors.Wrap(err, "fail to take message from standalone broker")
+ }
+
+ commitFunc := func(action connector.EventMeshAction) error {
+ switch action {
+ case connector.CommitMessage:
+ // update offset
+ w.offset.Store(message.GetOffset())
+ case connector.ReconsumeLater:
+ // No-Op
+ case connector.ManualAck:
+ // update offset
+ w.offset.Store(message.GetOffset())
+ default:
+ }
+ return nil
+ }
+
+ w.listener.Consume(message.event, commitFunc)
+ return nil
+}
+
+func (w *SubscribeWorker) Stop() {
+ w.quit <- struct{}{}
+}
diff --git a/plugin/connector/standalone/factory.go b/plugin/connector/standalone/factory.go
new file mode 100644
index 0000000..b9b1e5b
--- /dev/null
+++ b/plugin/connector/standalone/factory.go
@@ -0,0 +1,70 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package standalone
+
+import (
+ "errors"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector"
+)
+
+func init() {
+ plugin.Register("standalone", &Factory{})
+}
+
+type Factory struct {
+ plugin.Plugin
+ properties map[string]string
+ consumer connector.Consumer
+ producer connector.Producer
+}
+
+func (f *Factory) Type() string {
+ return connector.PluginType
+}
+
+func (f *Factory) Setup(name string, dec plugin.Decoder) error {
+ if dec == nil {
+ return errors.New("standalone config decoder empty")
+ }
+ properties := make(map[string]string)
+ if err := dec.Decode(properties); err != nil {
+ return err
+ }
+ f.properties = properties
+ consumer := NewConsumer()
+ consumer.InitConsumer(f.properties)
+ consumer.Start()
+ f.consumer = consumer
+
+ producer := NewProducer()
+ producer.InitProducer(f.properties)
+ producer.Start()
+ f.producer = producer
+ return nil
+}
+
+func (f *Factory) GetConsumer() (connector.Consumer, error) {
+ return f.consumer, nil
+}
+
+func (f *Factory) GetProducer() (connector.Producer, error) {
+ return f.producer, nil
+}
+
+func (f *Factory) GetResource() (connector.Resource, error) {
+ return &Resource{}, nil
+}
diff --git a/plugin/connector/standalone/message.go b/plugin/connector/standalone/message.go
new file mode 100644
index 0000000..943bb03
--- /dev/null
+++ b/plugin/connector/standalone/message.go
@@ -0,0 +1,47 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package standalone
+
+import (
+ ce "github.com/cloudevents/sdk-go/v2"
+ "strconv"
+)
+
+type Message struct {
+ createTimeMills int64
+ event *ce.Event
+}
+
+func (m *Message) GetTopicName() string {
+ return m.event.Subject()
+}
+
+func (m *Message) GetOffset() int64 {
+ return GetOffsetFromEvent(m.event)
+}
+
+// SetOffset since the CloudEvents go-sdk doesn't support int64 in v1 version, need to store offset by string
+func (m *Message) SetOffset(offset int64) {
+ m.event.SetExtension("offset", strconv.FormatInt(offset, 10))
+}
+
+func GetOffsetFromEvent(event *ce.Event) int64 {
+ offset, err := strconv.ParseInt(event.Extensions()["offset"].(string), 10, 64)
+ if err != nil {
+ panic(err)
+ }
+ return offset
+}
diff --git a/plugin/connector/standalone/producer.go b/plugin/connector/standalone/producer.go
new file mode 100644
index 0000000..2d07e6e
--- /dev/null
+++ b/plugin/connector/standalone/producer.go
@@ -0,0 +1,116 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package standalone
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "strconv"
+ "time"
+
+ ce "github.com/cloudevents/sdk-go/v2"
+ "go.uber.org/atomic"
+
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector"
+)
+
+// Producer standalone producer
+type Producer struct {
+ broker *Broker
+ started *atomic.Bool
+}
+
+func NewProducer() *Producer {
+ return &Producer{
+ broker: GetBroker(),
+ started: atomic.NewBool(false),
+ }
+}
+
+func (p *Producer) Publish(ctx context.Context, event *ce.Event, callback *connector.SendCallback) (err error) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = fmt.Errorf("callback function execute failed: %v", err)
+ }
+ }()
+
+ if p.IsClosed() {
+ err = errors.New("fail to publish message, producer has been closed")
+ return
+ }
+
+ message, err := p.broker.PutMessage(event.Subject(), event)
+ if err != nil {
+ callback.OnError(&connector.ErrorResult{
+ Topic: event.Subject(),
+ Err: err,
+ })
+ return
+ }
+ sendResult := connector.SendResult{
+ MessageId: strconv.FormatInt(message.GetOffset(), 10),
+ Topic: event.Subject(),
+ Err: nil,
+ }
+ callback.OnSuccess(&sendResult)
+ return
+}
+
+func (p *Producer) SendOneway(ctx context.Context, event *ce.Event) (err error) {
+ _, err = p.broker.PutMessage(event.Subject(), event)
+ return
+}
+
+func (p *Producer) Request(ctx context.Context, event *ce.Event, callback *connector.RequestReplyCallback, timeout time.Duration) error {
+ return fmt.Errorf("request is not supported in standalone connector")
+}
+
+func (p *Producer) Reply(ctx context.Context, event *ce.Event, callback *connector.SendCallback) error {
+ return fmt.Errorf("reply is not supported in standalone connector")
+}
+
+func (p *Producer) CheckTopicExist(topicName string) (exist bool, err error) {
+ return p.broker.ExistTopic(topicName), nil
+}
+
+func (p *Producer) SetExtFields() error {
+ // No-Op for standalone producer
+ return nil
+}
+
+func (p *Producer) InitProducer(properties map[string]string) error {
+ // No-Op for standalone producer
+ return nil
+}
+
+func (p *Producer) Start() error {
+ p.started.CAS(false, true)
+ return nil
+}
+
+func (p *Producer) Shutdown() error {
+ p.started.CAS(true, false)
+ return nil
+}
+
+func (p *Producer) IsStarted() bool {
+ return p.started.Load()
+}
+
+func (p *Producer) IsClosed() bool {
+ return !p.started.Load()
+}
diff --git a/plugin/connector/standalone/resource.go b/plugin/connector/standalone/resource.go
new file mode 100644
index 0000000..7ddcfe7
--- /dev/null
+++ b/plugin/connector/standalone/resource.go
@@ -0,0 +1,29 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package standalone
+
+type Resource struct {
+}
+
+func (r Resource) Init() error {
+ // No-Op for standalone resource
+ return nil
+}
+
+func (r Resource) Release() error {
+ // No-Op for standalone resource
+ return nil
+}
diff --git a/plugin/connector/standalone/standalone_connector_test.go b/plugin/connector/standalone/standalone_connector_test.go
new file mode 100644
index 0000000..c6f70b2
--- /dev/null
+++ b/plugin/connector/standalone/standalone_connector_test.go
@@ -0,0 +1,224 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package standalone
+
+import (
+ "context"
+ "fmt"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/connector"
+ ce "github.com/cloudevents/sdk-go/v2"
+ "github.com/google/uuid"
+ "github.com/stretchr/testify/assert"
+ "gopkg.in/yaml.v3"
+ "testing"
+)
+
+const (
+ topicName = "example-topic"
+ pluginName = "standalone"
+)
+
+// MockDecoder standalone connector properties mock decoder
+type MockDecoder struct {
+}
+
+// Decode mock decoder, no-op
+func (m *MockDecoder) Decode(cfg interface{}) error {
+ return nil
+}
+
+func TestProducer_Publish(t *testing.T) {
+ factory := &Factory{}
+ err := factory.Setup(pluginName, &plugin.YamlNodeDecoder{
+ Node: &yaml.Node{},
+ })
+ assert.NoError(t, err)
+ producer, _ := factory.GetProducer()
+ producer.Start()
+ defer producer.Shutdown()
+
+ var publishSuccess bool
+ var callBackErr error
+ topic := fmt.Sprintf("%s_publish", topicName)
+ callback := connector.SendCallback{
+ OnSuccess: func(result *connector.SendResult) {
+ publishSuccess = true
+ assert.Equal(t, topic, result.Topic)
+ assert.Equal(t, "1", result.MessageId)
+ assert.Nil(t, result.Err)
+ },
+ OnError: func(result *connector.ErrorResult) {
+ callBackErr = result.Err
+ },
+ }
+
+ err = producer.Publish(context.Background(), getTestEvent(topic), &callback)
+ assert.Nil(t, err)
+ assert.True(t, publishSuccess)
+ assert.Nil(t, callBackErr)
+
+ exist, err := producer.CheckTopicExist(topic)
+ assert.True(t, exist)
+ assert.Nil(t, err)
+
+}
+func TestConsumer_Subscribe(t *testing.T) {
+ done := make(chan struct{})
+ topic := fmt.Sprintf("%s_subscribe", topicName)
+ listener := connector.EventListener{
+ Consume: func(event *ce.Event, commitFunc connector.CommitFunc) error {
+ var data map[string]interface{}
+ event.DataAs(&data)
+ t.Log(event.String())
+ commitFunc(connector.CommitMessage)
+ done <- struct{}{}
+ return nil
+ },
+ }
+
+ factory := &Factory{}
+ err := factory.Setup(pluginName, &plugin.YamlNodeDecoder{
+ Node: &yaml.Node{},
+ })
+ assert.NoError(t, err)
+ consumer, _ := factory.GetConsumer()
+ consumer.Start()
+ consumer.RegisterEventListener(&listener)
+ consumer.Subscribe(topic)
+ defer consumer.Shutdown()
+
+ producer, _ := factory.GetProducer()
+ producer.Start()
+ defer producer.Shutdown()
+ err = producer.Publish(context.Background(), getTestEventOfData(topic, map[string]interface{}{
+ "val": "value",
+ }), getEmptyPublishCallback())
+ assert.NoError(t, err)
+ <-done
+}
+
+func TestConsumer_ManualAck(t *testing.T) {
+ done := make(chan struct{})
+ topic := fmt.Sprintf("%s_ack", topicName)
+ listener := connector.EventListener{
+ Consume: func(event *ce.Event, commitFunc connector.CommitFunc) error {
+ var data map[string]interface{}
+ event.DataAs(&data)
+ commitFunc(connector.ManualAck)
+ done <- struct{}{}
+ return nil
+ },
+ }
+
+ factory := &Factory{}
+ err := factory.Setup(pluginName, &plugin.YamlNodeDecoder{
+ Node: &yaml.Node{},
+ })
+ assert.NoError(t, err)
+ consumer, _ := factory.GetConsumer()
+ consumer.Start()
+ consumer.RegisterEventListener(&listener)
+ consumer.Subscribe(topic)
+ defer consumer.Shutdown()
+
+ producer, _ := factory.GetProducer()
+ producer.Start()
+ defer producer.Shutdown()
+ err = producer.Publish(context.Background(), getTestEventOfData(topic, map[string]interface{}{
+ "val": "test",
+ }), getEmptyPublishCallback())
+ assert.NoError(t, err)
+ <-done
+}
+
+// TODO update later
+func TestConsumer_UpdateOffset(t *testing.T) {
+ //sum := atomic.NewInt64(0)
+ //ch := make(chan struct{})
+ //listener := connector.EventListener{
+ // Consume: func(event *ce.Event, commitFunc connector.CommitFunc) error {
+ // var data map[string]interface{}
+ // event.DataAs(&data)
+ // sum.Add(int64(data["val"].(float64)))
+ // commitFunc(connector.CommitMessage)
+ // ch <- struct{}{}
+ // return nil
+ // },
+ //}
+ //
+ //factory := &Factory{}
+ //err := factory.Setup(pluginName, &plugin.YamlNodeDecoder{
+ // Node: &yaml.Node{},
+ //})
+ //assert.NoError(t, err)
+ //consumer, _ := factory.GetConsumer()
+ //consumer.Start()
+ //defer consumer.Shutdown()
+ //consumer.RegisterEventListener(&listener)
+ //event := getTestEvent()
+ //event.SetExtension("offset", "49")
+ //consumer.Subscribe(topicName)
+ //consumer.UpdateOffset(context.Background(), []*ce.Event{event})
+ //
+ //producer, _ := factory.GetProducer()
+ //producer.Start()
+ //defer producer.Shutdown()
+ //for i := 1; i <= 50; i++ {
+ // err := producer.Publish(context.Background(), getTestEventOfData(map[string]interface{}{
+ // "val": i,
+ // }), getEmptyPublishCallback())
+ //
+ // if err != nil {
+ // t.Fail()
+ // return
+ // }
+ //}
+ //
+ //timer := time.NewTimer(3 * time.Second)
+ //select {
+ //case <-timer.C:
+ // t.Fail()
+ //case <-ch:
+ // assert.Equal(t, int64(50), sum.Load())
+ //}
+}
+
+func getTestEvent(topicName string) *ce.Event {
+ event := ce.NewEvent()
+ event.SetID(uuid.New().String())
+ event.SetSubject(topicName)
+ return &event
+}
+
+func getTestEventOfData(topicName string, data map[string]interface{}) *ce.Event {
+ event := ce.NewEvent()
+ event.SetID(uuid.New().String())
+ event.SetSubject(topicName)
+ event.SetData(ce.ApplicationJSON, data)
+ return &event
+}
+
+func getEmptyPublishCallback() *connector.SendCallback {
+ return &connector.SendCallback{
+ OnSuccess: func(result *connector.SendResult) {
+ // No-Op
+ },
+ OnError: func(result *connector.ErrorResult) {
+ panic(result.Err)
+ },
+ }
+}
diff --git a/plugin/database/mysql/mysql.go b/plugin/database/mysql/mysql.go
new file mode 100644
index 0000000..ca88462
--- /dev/null
+++ b/plugin/database/mysql/mysql.go
@@ -0,0 +1,68 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package mysql
+
+import (
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+)
+
+const (
+ PluginType = "database"
+ PluginName = "mysql"
+)
+
+const (
+ defaultDriverName = "mysql"
+)
+
+// PluginConfig global mysql plugin configuration
+var PluginConfig Config
+
+func init() {
+ plugin.Register(PluginName, &MysqlPlugin{})
+}
+
+// Config mysql proxy config
+type Config struct {
+ DSN string `yaml:"dsn"`
+ MaxIdle int `yaml:"max_idle"`
+ MaxOpen int `yaml:"max_open"`
+ MaxLifetime int `yaml:"max_lifetime"`
+ DriverName string `yaml:"driver_name"`
+}
+
+func (c *Config) setDefault() {
+ if c.DriverName == "" {
+ c.DriverName = defaultDriverName
+ }
+}
+
+// MysqlPlugin load mysql plugin configuration
+type MysqlPlugin struct{}
+
+// Type plugin type
+func (m *MysqlPlugin) Type() string {
+ return PluginType
+}
+
+// Setup setup config mysql connect configuration (integration with gorm)
+func (m *MysqlPlugin) Setup(name string, configDesc plugin.Decoder) (err error) {
+ if err = configDesc.Decode(&PluginConfig); err != nil {
+ return
+ }
+ PluginConfig.setDefault()
+ return nil
+}
diff --git a/plugin/metrics/metrics.go b/plugin/metrics/metrics.go
new file mode 100644
index 0000000..3184e92
--- /dev/null
+++ b/plugin/metrics/metrics.go
@@ -0,0 +1,36 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package metrics
+
+import "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+
+const (
+ PluginType = "metrics"
+)
+
+// Metrics plugin interface
+type Metrics interface {
+ plugin.Plugin
+
+ // IncCount inc count by name and label
+ IncCount(name string, label string)
+
+ // AddCount add count by name and label
+ AddCount(name string, label string, val float64)
+
+ // RecordLatency record latency by name and label
+ RecordLatency(name string, label string, latency float64)
+}
diff --git a/plugin/metrics/prometheus/config.go b/plugin/metrics/prometheus/config.go
new file mode 100644
index 0000000..03137a5
--- /dev/null
+++ b/plugin/metrics/prometheus/config.go
@@ -0,0 +1,26 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package prometheus
+
+type Config struct {
+ EndpointPort string `yaml:"endpoint_port"`
+}
+
+func NewDefaultConfig() *Config {
+ return &Config{
+ EndpointPort: "19090",
+ }
+}
diff --git a/plugin/metrics/prometheus/metrics.go b/plugin/metrics/prometheus/metrics.go
new file mode 100644
index 0000000..0febf7c
--- /dev/null
+++ b/plugin/metrics/prometheus/metrics.go
@@ -0,0 +1,153 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package prometheus
+
+import (
+ "fmt"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/metrics"
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+ "net/http"
+ "sync"
+)
+
+func init() {
+ plugin.Register("prometheus", getDefaultMetrics())
+}
+
+func getDefaultMetrics() *Metrics {
+ return &Metrics{
+ counters: make(map[string]prometheus.Collector),
+ histograms: make(map[string]prometheus.Collector)}
+}
+
+type Metrics struct {
+ counters map[string]prometheus.Collector
+ histograms map[string]prometheus.Collector
+ lock sync.Mutex
+ once sync.Once
+ config *Config
+}
+
+const nameSpace = "eventmesh"
+const subSystem = "runtime"
+
+const (
+ histogram = iota
+ counter
+)
+
+func (p *Metrics) Type() string {
+ return metrics.PluginType
+}
+
+func (p *Metrics) Setup(name string, dec plugin.Decoder) error {
+ config := NewDefaultConfig()
+ err := dec.Decode(config)
+ if err != nil {
+ return err
+ }
+ p.Init(config)
+ return nil
+}
+
+// Init try to init metrics, include exposing http endpoint
+func (p *Metrics) Init(config *Config) {
+ p.once.Do(func() {
+ p.config = config
+ p.exposeEndpoint()
+ })
+}
+
+// exposeEndpoint expose http endpoint
+func (p *Metrics) exposeEndpoint() {
+ go func() {
+ http.Handle("/metrics", promhttp.Handler())
+ http.ListenAndServe(fmt.Sprintf(":%s", p.config.EndpointPort), nil)
+ }()
+}
+
+// loadCollector load collector by name and collectorType
+func (p *Metrics) loadCollector(name string, collectorType int) prometheus.Collector {
+ if collector := p.getCollectorByNameAndType(name, collectorType); collector != nil {
+ return collector
+ }
+ return p.registerNewCollector(name, collectorType)
+}
+
+func (p *Metrics) getCollectorByNameAndType(name string, collectorType int) prometheus.Collector {
+ switch collectorType {
+ case counter:
+ return p.counters[name]
+ case histogram:
+ return p.histograms[name]
+ default:
+ panic("prometheus metrics plugin get collector error, illegal collector type")
+ }
+}
+
+// registerNewCollector create and register new collector of collectorType
+func (p *Metrics) registerNewCollector(name string, collectorType int) prometheus.Collector {
+ p.lock.Lock()
+ defer p.lock.Unlock()
+
+ if collector := p.getCollectorByNameAndType(name, collectorType); collector != nil {
+ return collector
+ }
+
+ var collector prometheus.Collector
+ switch collectorType {
+ case counter:
+ collector = prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Namespace: nameSpace,
+ Subsystem: subSystem,
+ Name: name,
+ }, []string{"label"},
+ )
+ p.counters[name] = collector
+ case histogram:
+ collector = prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Namespace: nameSpace,
+ Subsystem: subSystem,
+ Name: name,
+ Buckets: prometheus.ExponentialBuckets(1, 2, 13),
+ }, []string{"label"},
+ )
+ p.histograms[name] = collector
+ default:
+ panic("prometheus metrics plugin register collector error, illegal collector type")
+ }
+ prometheus.MustRegister(collector)
+ return collector
+}
+
+func (p *Metrics) IncCount(name string, label string) {
+ collector := p.loadCollector(name, counter).(*prometheus.CounterVec)
+ collector.With(prometheus.Labels{"label": label}).Inc()
+}
+
+func (p *Metrics) AddCount(name string, label string, val float64) {
+ collector := p.loadCollector(name, counter).(*prometheus.CounterVec)
+ collector.With(prometheus.Labels{"label": label}).Add(val)
+}
+
+func (p *Metrics) RecordLatency(name string, label string, latency float64) {
+ collector := p.loadCollector(name, histogram).(*prometheus.HistogramVec)
+ collector.With(prometheus.Labels{"label": label}).Observe(latency)
+}
diff --git a/plugin/metrics/prometheus/metrics_test.go b/plugin/metrics/prometheus/metrics_test.go
new file mode 100644
index 0000000..5aa7ac5
--- /dev/null
+++ b/plugin/metrics/prometheus/metrics_test.go
@@ -0,0 +1,30 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package prometheus
+
+import (
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+ mtr "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/metrics"
+ "testing"
+)
+
+func TestCounterAndLatency(t *testing.T) {
+ metrics := plugin.Get("metrics", "prometheus").(mtr.Metrics)
+ metrics.(*Metrics).Init(NewDefaultConfig())
+ metrics.IncCount("eventmesh_tcp_retry_queue_size", "")
+ metrics.AddCount("eventmesh_tcp_connection_num", "", 10)
+ metrics.RecordLatency("eventmesh_http_request_cost", "", 1000)
+}
diff --git a/plugin/plugin.go b/plugin/plugin.go
new file mode 100644
index 0000000..9cb0605
--- /dev/null
+++ b/plugin/plugin.go
@@ -0,0 +1,66 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// Package plugin implements a general plugin factory system which provides plugin registration and loading.
+// It is mainly used when certain plugins must be loaded by configuration.
+// This system is not supposed to register plugins that do not rely on configuration like codec. Instead, plugins
+// that do not rely on configuration should be registered by calling methods in certain packages.
+package plugin
+
+var plugins = make(map[string]map[string]Plugin) // plugin type => { plugin name => plugin factory }
+
+var activePlugins = make(map[string]string) // store the active plugins{ plugin type => plugin name}
+
+// Plugin is the interface for plugin factory abstraction.
+// Custom Plugins need to implement this interface to be registered as a plugin with certain type.
+type Plugin interface {
+ // Type returns type of the plugin, i.e. selector, log, config, tracing.
+ Type() string
+ // Setup loads plugin by configuration.
+ // The data structure of the configuration of the plugin needs to be defined in advance。
+ Setup(name string, dec Decoder) error
+}
+
+// Decoder is the interface used to decode plugin configuration.
+type Decoder interface {
+ Decode(cfg interface{}) error // the input param is the custom configuration of the plugin
+}
+
+// Register registers a plugin factory.
+// Name of the plugin should be specified.
+// It is supported to register instances which are the same implementation of plugin Plugin
+// but use different configuration.
+func Register(name string, f Plugin) {
+ factories, ok := plugins[f.Type()]
+ if !ok {
+ factories = make(map[string]Plugin)
+ plugins[f.Type()] = factories
+ }
+ factories[name] = f
+}
+
+func SetActivePlugin(ap map[string]string) {
+ activePlugins = ap
+}
+
+// Get returns a plugin Plugin by its type and name.
+func Get(typ string, name string) Plugin {
+ return plugins[typ][name]
+}
+
+// GetByType return the single plugin
+func GetByType(typ string) Plugin {
+ return Get(typ, activePlugins[typ])
+}
diff --git a/plugin/protocol/cloudevents/Readme.md b/plugin/protocol/cloudevents/Readme.md
new file mode 100644
index 0000000..6f2089a
--- /dev/null
+++ b/plugin/protocol/cloudevents/Readme.md
@@ -0,0 +1 @@
+transfer the message with cloudevents protocol
\ No newline at end of file
diff --git a/plugin/protocol/cloudevents/cloudevents.go b/plugin/protocol/cloudevents/cloudevents.go
new file mode 100644
index 0000000..f1a52ca
--- /dev/null
+++ b/plugin/protocol/cloudevents/cloudevents.go
@@ -0,0 +1,215 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package cloudevents
+
+import (
+ "context"
+ "fmt"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/pkg/common/protocol/grpc"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/pkg/common/protocol/tcp"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/protocol"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/runtime/consts"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/runtime/proto/pb"
+ "github.com/cloudevents/sdk-go/v2"
+ "github.com/cloudevents/sdk-go/v2/event"
+ "github.com/cloudevents/sdk-go/v2/event/datacodec"
+)
+
+func init() {
+ plugin.Register(protocol.CloudEvents, &CloudeventsPlugin{})
+}
+
+// CloudeventsPlugin CloudEvents protocol adaptor
+// used to transform CloudEvents message to CloudEvents message.
+type CloudeventsPlugin struct {
+}
+
+func (c *CloudeventsPlugin) Type() string {
+ return plugin.Protocol
+}
+
+func (c *CloudeventsPlugin) Setup(name string, dec plugin.Decoder) error {
+ return nil
+}
+
+func (c *CloudeventsPlugin) ToCloudEvent(msg interface{}) (*v2.Event, error) {
+ switch msg.(type) {
+ case *tcp.Package:
+ pck := msg.(*tcp.Package)
+ return c.deserializeTcpProtocol(pck)
+ case *grpc.SimpleMessageWrapper:
+ sw := msg.(*grpc.SimpleMessageWrapper)
+ sm := sw.SimpleMessage
+ return deserializeGrpcProtocol(sm)
+ }
+ panic("implement me")
+}
+
+func (c *CloudeventsPlugin) ToCloudEvents(msg interface{}) ([]*v2.Event, error) {
+ bmw := msg.(*grpc.BatchMessageWrapper)
+ return buildBatchMessage(bmw.BatchMessage)
+}
+
+func (c *CloudeventsPlugin) FromCloudEvent(event *v2.Event) (interface{}, error) {
+ desc := event.Extensions()[grpc.PROTOCOL_DESC].(string)
+ if desc == "grpc" {
+ return buildSimpleMessage(event)
+ }
+ return nil, fmt.Errorf("only grpc supported now")
+}
+
+func (c *CloudeventsPlugin) ProtocolType() string {
+ //TODO implement me
+ panic("implement me")
+}
+
+func (c *CloudeventsPlugin) deserializeTcpProtocol(pck *tcp.Package) (*v2.Event, error) {
+ // TODO add when support tcp procotol
+ panic("implement me")
+}
+
+func defaultIfEmpty(in string, def interface{}) string {
+ if in == "" {
+ return def.(string)
+ }
+ return in
+}
+
+func defaultIfNil(def string, in interface{}) string {
+ if in == nil {
+ return def
+ }
+ return in.(string)
+}
+
+func deserializeGrpcProtocol(sm *pb.SimpleMessage) (*v2.Event, error) {
+ content := sm.Content
+ ct, ok := sm.Properties[grpc.CONTENT_TYPE]
+ if !ok {
+ ct = consts.CONTENT_TYPE_CLOUDEVENTS_JSON
+ }
+ evt := v2.NewEvent()
+ if err := datacodec.Decode(context.TODO(), ct, []byte(content), &evt); err != nil {
+ return nil, err
+ }
+ hdr := sm.Header
+ result := v2.NewEvent()
+ ver := defaultIfEmpty(hdr.ProtocolVersion, evt.Extensions()[grpc.PROTOCOL_VERSION])
+ topic := defaultIfEmpty(sm.Topic, evt.Subject())
+ result.SetExtension(grpc.ENV, defaultIfEmpty(hdr.Env, evt.Extensions()[grpc.ENV]))
+ result.SetExtension(grpc.IDC, defaultIfEmpty(hdr.Idc, evt.Extensions()[grpc.IDC]))
+ result.SetExtension(grpc.IP, defaultIfEmpty(hdr.Ip, evt.Extensions()[grpc.IP]))
+ result.SetExtension(grpc.PID, defaultIfEmpty(hdr.Pid, evt.Extensions()[grpc.PID]))
+ result.SetExtension(grpc.SYS, defaultIfEmpty(hdr.Sys, evt.Extensions()[grpc.SYS]))
+ result.SetExtension(grpc.LANGUAGE, defaultIfEmpty(hdr.Language, evt.Extensions()[grpc.LANGUAGE]))
+ result.SetExtension(grpc.PROTOCOL_TYPE, defaultIfEmpty(hdr.ProtocolType, evt.Extensions()[grpc.PROTOCOL_TYPE]))
+ result.SetExtension(grpc.PROTOCOL_DESC, defaultIfEmpty(hdr.ProtocolDesc, evt.Extensions()[grpc.PROTOCOL_DESC]))
+ result.SetExtension(grpc.PROTOCOL_VERSION, defaultIfEmpty(hdr.ProtocolVersion, evt.Extensions()[grpc.PROTOCOL_VERSION]))
+ result.SetExtension(grpc.UNIQUE_ID, defaultIfEmpty(sm.UniqueId, evt.Extensions()[grpc.UNIQUE_ID]))
+ result.SetExtension(grpc.SEQ_NUM, defaultIfEmpty(sm.SeqNum, evt.Extensions()[grpc.SEQ_NUM]))
+ result.SetExtension(grpc.USERNAME, defaultIfEmpty(hdr.Username, evt.Extensions()[grpc.USERNAME]))
+ result.SetExtension(grpc.PASSWD, defaultIfEmpty(hdr.Password, evt.Extensions()[grpc.PASSWD]))
+ result.SetExtension(grpc.TTL, defaultIfEmpty(sm.Ttl, evt.Extensions()[grpc.TTL]))
+ result.SetExtension(grpc.PRODUCERGROUP, defaultIfEmpty(sm.ProducerGroup, evt.Extensions()[grpc.PRODUCERGROUP]))
+
+ if ver == event.CloudEventsVersionV1 {
+ result.SetSpecVersion(event.CloudEventsVersionV1)
+ } else {
+ result.SetSpecVersion(event.CloudEventsVersionV03)
+ }
+ result.SetSubject(topic)
+
+ return &result, nil
+}
+
+func buildBatchMessage(bm *pb.BatchMessage) ([]*v2.Event, error) {
+ var msgs []*v2.Event
+ hdr := bm.Header
+
+ for _, item := range bm.MessageItem {
+ content := item.Content
+ ct, ok := item.Properties[grpc.CONTENT_TYPE]
+ if !ok {
+ ct = consts.CONTENT_TYPE_CLOUDEVENTS_JSON
+ }
+ evt := v2.NewEvent()
+ if err := datacodec.Decode(context.TODO(), ct, []byte(content), &evt); err != nil {
+ return nil, err
+ }
+ result := v2.NewEvent()
+ ver := defaultIfEmpty(hdr.ProtocolVersion, evt.Extensions()[grpc.PROTOCOL_VERSION])
+ topic := defaultIfEmpty(bm.Topic, evt.Subject())
+ result.SetExtension(grpc.ENV, defaultIfEmpty(hdr.Env, evt.Extensions()[grpc.ENV]))
+ result.SetExtension(grpc.IDC, defaultIfEmpty(hdr.Idc, evt.Extensions()[grpc.IDC]))
+ result.SetExtension(grpc.IP, defaultIfEmpty(hdr.Ip, evt.Extensions()[grpc.IP]))
+ result.SetExtension(grpc.PID, defaultIfEmpty(hdr.Pid, evt.Extensions()[grpc.PID]))
+ result.SetExtension(grpc.SYS, defaultIfEmpty(hdr.Sys, evt.Extensions()[grpc.SYS]))
+ result.SetExtension(grpc.LANGUAGE, defaultIfEmpty(hdr.Language, evt.Extensions()[grpc.LANGUAGE]))
+ result.SetExtension(grpc.PROTOCOL_TYPE, defaultIfEmpty(hdr.ProtocolType, evt.Extensions()[grpc.PROTOCOL_TYPE]))
+ result.SetExtension(grpc.PROTOCOL_DESC, defaultIfEmpty(hdr.ProtocolDesc, evt.Extensions()[grpc.PROTOCOL_DESC]))
+ result.SetExtension(grpc.PROTOCOL_VERSION, defaultIfEmpty(hdr.ProtocolVersion, evt.Extensions()[grpc.PROTOCOL_VERSION]))
+ result.SetExtension(grpc.UNIQUE_ID, defaultIfEmpty(item.UniqueId, evt.Extensions()[grpc.UNIQUE_ID]))
+ result.SetExtension(grpc.SEQ_NUM, defaultIfEmpty(item.SeqNum, evt.Extensions()[grpc.SEQ_NUM]))
+ result.SetExtension(grpc.USERNAME, defaultIfEmpty(hdr.Username, evt.Extensions()[grpc.USERNAME]))
+ result.SetExtension(grpc.PASSWD, defaultIfEmpty(hdr.Password, evt.Extensions()[grpc.PASSWD]))
+ result.SetExtension(grpc.TTL, defaultIfEmpty(item.Ttl, evt.Extensions()[grpc.TTL]))
+ result.SetExtension(grpc.PRODUCERGROUP, defaultIfEmpty(bm.ProducerGroup, evt.Extensions()[grpc.PRODUCERGROUP]))
+ if ver == event.CloudEventsVersionV1 {
+ result.SetSpecVersion(event.CloudEventsVersionV1)
+ } else {
+ result.SetSpecVersion(event.CloudEventsVersionV03)
+ }
+ result.SetSubject(topic)
+ msgs = append(msgs, &result)
+ }
+ return msgs, nil
+}
+
+func buildSimpleMessage(evt *v2.Event) (*grpc.SimpleMessageWrapper, error) {
+ ct, err := datacodec.Encode(context.TODO(), evt.DataContentType(), evt)
+ if err != nil {
+ return nil, err
+ }
+ hdr := &pb.RequestHeader{
+ Env: defaultIfNil("env", evt.Extensions()[grpc.ENV]),
+ Idc: defaultIfNil("idc", evt.Extensions()[grpc.IDC]),
+ Ip: defaultIfNil("127.0.0.1", evt.Extensions()[grpc.IP]),
+ Pid: defaultIfNil("123", evt.Extensions()[grpc.PID]),
+ Sys: defaultIfNil("sys123", evt.Extensions()[grpc.SYS]),
+ Username: defaultIfNil("user", evt.Extensions()[grpc.USERNAME]),
+ Password: defaultIfNil("pass", evt.Extensions()[grpc.PASSWD]),
+ Language: defaultIfNil("JAVA", evt.Extensions()[grpc.LANGUAGE]),
+ ProtocolType: defaultIfNil("protocol", evt.Extensions()[grpc.PROTOCOL_TYPE]),
+ ProtocolDesc: defaultIfNil("protocolDesc", evt.Extensions()[grpc.PROTOCOL_DESC]),
+ ProtocolVersion: defaultIfNil("1.0", evt.Extensions()[grpc.PROTOCOL_VERSION]),
+ }
+ msg := &pb.SimpleMessage{
+ Header: hdr,
+ Content: string(ct),
+ ProducerGroup: defaultIfNil("producerGroup", evt.Extensions()[grpc.PRODUCERGROUP]),
+ SeqNum: defaultIfNil("", evt.Extensions()[grpc.SEQ_NUM]),
+ UniqueId: defaultIfNil("", evt.Extensions()[grpc.UNIQUE_ID]),
+ Topic: evt.Subject(),
+ Ttl: defaultIfNil("3000", evt.Extensions()[grpc.TTL]),
+ Properties: map[string]string{grpc.CONTENT_TYPE: evt.DataContentType()},
+ }
+ for k, v := range evt.Extensions() {
+ msg.Properties[k] = defaultIfNil("", v)
+ }
+
+ return &grpc.SimpleMessageWrapper{SimpleMessage: msg}, nil
+}
diff --git a/plugin/protocol/protocol.go b/plugin/protocol/protocol.go
new file mode 100644
index 0000000..e06b35c
--- /dev/null
+++ b/plugin/protocol/protocol.go
@@ -0,0 +1,40 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package protocol
+
+import (
+ cloudv2 "github.com/cloudevents/sdk-go/v2"
+)
+
+var (
+ CloudEvents = "cloudevents"
+)
+
+// Adapter protocol adapter
+// transfer the message with given protocol and events message
+type Adapter interface {
+ // ToCloudEvent transform protocol to CloudEvent
+ ToCloudEvent(interface{}) (*cloudv2.Event, error)
+
+ //ToCloudEvents transform protocol to CloudEvent list.
+ ToCloudEvents(interface{}) ([]*cloudv2.Event, error)
+
+ //FromCloudEvent transfor CloudEvent to target protocol.
+ FromCloudEvent(event *cloudv2.Event) (interface{}, error)
+
+ // ProtocolType protocol type, protocol type should not be null
+ ProtocolType() string
+}
diff --git a/plugin/registry/model.go b/plugin/registry/model.go
new file mode 100644
index 0000000..4953312
--- /dev/null
+++ b/plugin/registry/model.go
@@ -0,0 +1,42 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package registry
+
+import "time"
+
+type EventMeshDataInfo struct {
+ EventMeshClusterName string
+ EventMeshName string
+ Endpoint string
+ LastUpdateTimestamp time.Time
+ Metadata map[string]string
+}
+
+type EventMeshRegisterInfo struct {
+ EventMeshClusterName string
+ EventMeshName string
+ EndPoint string
+ EventMeshInstanceNumMap map[string]map[string]int
+ Metadata map[string]string
+ ProtocolType string
+}
+
+type EventMeshUnRegisterInfo struct {
+ EventMeshClusterName string
+ EventMeshName string
+ EndPoint string
+ ProtocolType string
+}
diff --git a/plugin/registry/nacos/config.go b/plugin/registry/nacos/config.go
new file mode 100644
index 0000000..51a9574
--- /dev/null
+++ b/plugin/registry/nacos/config.go
@@ -0,0 +1,24 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package nacos
+
+// Config define registry nacos plugin config
+type Config struct {
+ ServiceName string `yaml:"service-name"`
+ CacheDir string `yaml:"cache-dir"`
+ Port string `yaml:"port"` // nacos server port
+ AddressList string `yaml:"address-list"` // nacos server address list
+}
diff --git a/plugin/registry/nacos/mocks/mock_naming_client_interface.go b/plugin/registry/nacos/mocks/mock_naming_client_interface.go
new file mode 100644
index 0000000..84082c4
--- /dev/null
+++ b/plugin/registry/nacos/mocks/mock_naming_client_interface.go
@@ -0,0 +1,211 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// Code generated by MockGen. DO NOT EDIT.
+// Source: ./naming_client_interface.go
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+ reflect "reflect"
+
+ gomock "github.com/golang/mock/gomock"
+ model "github.com/nacos-group/nacos-sdk-go/v2/model"
+ vo "github.com/nacos-group/nacos-sdk-go/v2/vo"
+)
+
+// MockINamingClient is a mock of INamingClient interface.
+type MockINamingClient struct {
+ ctrl *gomock.Controller
+ recorder *MockINamingClientMockRecorder
+}
+
+// MockINamingClientMockRecorder is the mock recorder for MockINamingClient.
+type MockINamingClientMockRecorder struct {
+ mock *MockINamingClient
+}
+
+// NewMockINamingClient creates a new mock instance.
+func NewMockINamingClient(ctrl *gomock.Controller) *MockINamingClient {
+ mock := &MockINamingClient{ctrl: ctrl}
+ mock.recorder = &MockINamingClientMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockINamingClient) EXPECT() *MockINamingClientMockRecorder {
+ return m.recorder
+}
+
+// CloseClient mocks base method.
+func (m *MockINamingClient) CloseClient() {
+ m.ctrl.T.Helper()
+ m.ctrl.Call(m, "CloseClient")
+}
+
+// CloseClient indicates an expected call of CloseClient.
+func (mr *MockINamingClientMockRecorder) CloseClient() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseClient", reflect.TypeOf((*MockINamingClient)(nil).CloseClient))
+}
+
+// DeregisterInstance mocks base method.
+func (m *MockINamingClient) DeregisterInstance(param vo.DeregisterInstanceParam) (bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "DeregisterInstance", param)
+ ret0, _ := ret[0].(bool)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// DeregisterInstance indicates an expected call of DeregisterInstance.
+func (mr *MockINamingClientMockRecorder) DeregisterInstance(param interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeregisterInstance", reflect.TypeOf((*MockINamingClient)(nil).DeregisterInstance), param)
+}
+
+// GetAllServicesInfo mocks base method.
+func (m *MockINamingClient) GetAllServicesInfo(param vo.GetAllServiceInfoParam) (model.ServiceList, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetAllServicesInfo", param)
+ ret0, _ := ret[0].(model.ServiceList)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetAllServicesInfo indicates an expected call of GetAllServicesInfo.
+func (mr *MockINamingClientMockRecorder) GetAllServicesInfo(param interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllServicesInfo", reflect.TypeOf((*MockINamingClient)(nil).GetAllServicesInfo), param)
+}
+
+// GetService mocks base method.
+func (m *MockINamingClient) GetService(param vo.GetServiceParam) (model.Service, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetService", param)
+ ret0, _ := ret[0].(model.Service)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetService indicates an expected call of GetService.
+func (mr *MockINamingClientMockRecorder) GetService(param interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetService", reflect.TypeOf((*MockINamingClient)(nil).GetService), param)
+}
+
+// RegisterInstance mocks base method.
+func (m *MockINamingClient) RegisterInstance(param vo.RegisterInstanceParam) (bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "RegisterInstance", param)
+ ret0, _ := ret[0].(bool)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// RegisterInstance indicates an expected call of RegisterInstance.
+func (mr *MockINamingClientMockRecorder) RegisterInstance(param interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterInstance", reflect.TypeOf((*MockINamingClient)(nil).RegisterInstance), param)
+}
+
+// SelectAllInstances mocks base method.
+func (m *MockINamingClient) SelectAllInstances(param vo.SelectAllInstancesParam) ([]model.Instance, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SelectAllInstances", param)
+ ret0, _ := ret[0].([]model.Instance)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SelectAllInstances indicates an expected call of SelectAllInstances.
+func (mr *MockINamingClientMockRecorder) SelectAllInstances(param interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectAllInstances", reflect.TypeOf((*MockINamingClient)(nil).SelectAllInstances), param)
+}
+
+// SelectInstances mocks base method.
+func (m *MockINamingClient) SelectInstances(param vo.SelectInstancesParam) ([]model.Instance, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SelectInstances", param)
+ ret0, _ := ret[0].([]model.Instance)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SelectInstances indicates an expected call of SelectInstances.
+func (mr *MockINamingClientMockRecorder) SelectInstances(param interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectInstances", reflect.TypeOf((*MockINamingClient)(nil).SelectInstances), param)
+}
+
+// SelectOneHealthyInstance mocks base method.
+func (m *MockINamingClient) SelectOneHealthyInstance(param vo.SelectOneHealthInstanceParam) (*model.Instance, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "SelectOneHealthyInstance", param)
+ ret0, _ := ret[0].(*model.Instance)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// SelectOneHealthyInstance indicates an expected call of SelectOneHealthyInstance.
+func (mr *MockINamingClientMockRecorder) SelectOneHealthyInstance(param interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectOneHealthyInstance", reflect.TypeOf((*MockINamingClient)(nil).SelectOneHealthyInstance), param)
+}
+
+// Subscribe mocks base method.
+func (m *MockINamingClient) Subscribe(param *vo.SubscribeParam) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Subscribe", param)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Subscribe indicates an expected call of Subscribe.
+func (mr *MockINamingClientMockRecorder) Subscribe(param interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockINamingClient)(nil).Subscribe), param)
+}
+
+// Unsubscribe mocks base method.
+func (m *MockINamingClient) Unsubscribe(param *vo.SubscribeParam) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "Unsubscribe", param)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// Unsubscribe indicates an expected call of Unsubscribe.
+func (mr *MockINamingClientMockRecorder) Unsubscribe(param interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unsubscribe", reflect.TypeOf((*MockINamingClient)(nil).Unsubscribe), param)
+}
+
+// UpdateInstance mocks base method.
+func (m *MockINamingClient) UpdateInstance(param vo.UpdateInstanceParam) (bool, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "UpdateInstance", param)
+ ret0, _ := ret[0].(bool)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// UpdateInstance indicates an expected call of UpdateInstance.
+func (mr *MockINamingClientMockRecorder) UpdateInstance(param interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateInstance", reflect.TypeOf((*MockINamingClient)(nil).UpdateInstance), param)
+}
diff --git a/plugin/registry/nacos/nacos.go b/plugin/registry/nacos/nacos.go
new file mode 100644
index 0000000..052fc19
--- /dev/null
+++ b/plugin/registry/nacos/nacos.go
@@ -0,0 +1,241 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package nacos
+
+import (
+ "fmt"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/config"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/registry"
+ "github.com/gogf/gf/util/gconv"
+ "github.com/nacos-group/nacos-sdk-go/v2/clients"
+ "github.com/nacos-group/nacos-sdk-go/v2/clients/naming_client"
+ "github.com/nacos-group/nacos-sdk-go/v2/common/constant"
+ "github.com/nacos-group/nacos-sdk-go/v2/vo"
+ "go.uber.org/atomic"
+ "net"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+)
+
+var (
+ defaultConnectTimeout = 5000 * time.Millisecond
+ DefaultClusterName = "DEFAULT"
+ DefaultGroupName = "DEFAULT_GROUP"
+ DefaultWeight float64 = 10
+ protoList = []string{"TCP", "HTTP", "GRPC"}
+)
+
+func init() {
+ plugin.Register("nacos", &Registry{})
+}
+
+type Registry struct {
+ initStatus *atomic.Bool
+ startStatus *atomic.Bool
+ cfg *Config
+ client naming_client.INamingClient
+ registryInfos *sync.Map // map[string]*registry.EventMeshRegisterInfo
+}
+
+func (r *Registry) Type() string {
+ return registry.Type
+}
+
+func (r *Registry) Setup(name string, dec plugin.Decoder) error {
+ conf := &Config{}
+ if err := dec.Decode(conf); err != nil {
+ return err
+ }
+ r.cfg = conf
+ return nil
+}
+
+func (r *Registry) Init() error {
+ r.initStatus = atomic.NewBool(true)
+ r.startStatus = atomic.NewBool(false)
+ r.registryInfos = new(sync.Map)
+ return nil
+}
+
+func (r *Registry) Start() error {
+ var p vo.NacosClientParam
+ var addresses []string
+
+ if len(r.cfg.AddressList) > 0 {
+ addresses = strings.Split(r.cfg.AddressList, ",")
+ }
+
+ for _, address := range addresses {
+ ip, port, err := net.SplitHostPort(address)
+ if err != nil {
+ return err
+ }
+ p.ServerConfigs = append(p.ServerConfigs, constant.ServerConfig{IpAddr: ip, Port: gconv.Uint64(port)})
+ }
+
+ p.ClientConfig = &constant.ClientConfig{
+ TimeoutMs: uint64(defaultConnectTimeout.Milliseconds()),
+ CacheDir: r.cfg.CacheDir,
+ }
+ cli, err := clients.NewNamingClient(p)
+ if err != nil {
+ return err
+ }
+
+ r.startStatus.Store(true)
+ r.client = cli
+ return nil
+}
+
+func (r *Registry) Shutdown() error {
+ r.startStatus.Store(false)
+ r.initStatus.Store(false)
+ r.client.CloseClient()
+ return nil
+}
+
+func (r *Registry) FindEventMeshInfoByCluster(clusterName string) ([]*registry.EventMeshDataInfo, error) {
+ var infos []*registry.EventMeshDataInfo
+ meshServerName := config.GlobalConfig().Common.Name
+ cluster := config.GlobalConfig().Common.Cluster
+ for _, proto := range protoList {
+ ins, err := r.client.SelectInstances(vo.SelectInstancesParam{
+ Clusters: []string{clusterName},
+ ServiceName: fmt.Sprintf("%v-%v", meshServerName, proto),
+ GroupName: cluster,
+ HealthyOnly: true,
+ })
+ if err != nil {
+ return nil, err
+ }
+ if len(ins) == 0 {
+ continue
+ }
+ for _, in := range ins {
+ infos = append(infos, ®istry.EventMeshDataInfo{
+ EventMeshClusterName: in.ClusterName,
+ EventMeshName: in.ServiceName,
+ Endpoint: fmt.Sprintf("%v:%v", in.Ip, in.Port),
+ LastUpdateTimestamp: time.Time{},
+ Metadata: in.Metadata,
+ })
+ }
+ }
+
+ return infos, nil
+}
+
+func (r *Registry) FindAllEventMeshInfo() ([]*registry.EventMeshDataInfo, error) {
+ var infos []*registry.EventMeshDataInfo
+ meshServerName := config.GlobalConfig().Common.Name
+
+ for _, proto := range protoList {
+ ins, err := r.client.SelectInstances(vo.SelectInstancesParam{
+ Clusters: []string{},
+ ServiceName: fmt.Sprintf("%v-%v", meshServerName, proto),
+ GroupName: "GROUP",
+ HealthyOnly: true,
+ })
+ if err != nil {
+ return nil, err
+ }
+ if len(ins) == 0 {
+ continue
+ }
+ for _, in := range ins {
+ infos = append(infos, ®istry.EventMeshDataInfo{
+ EventMeshClusterName: in.ClusterName,
+ EventMeshName: in.ServiceName,
+ Endpoint: fmt.Sprintf("%v:%v", in.Ip, in.Port),
+ LastUpdateTimestamp: time.Time{},
+ Metadata: in.Metadata,
+ })
+ }
+ }
+
+ return infos, nil
+}
+
+// FindEventMeshClientDistributionData not used
+// deprecate
+func (r *Registry) FindEventMeshClientDistributionData(clusterName, group, purpose string) (map[string]map[string]int, error) {
+ return nil, nil
+}
+
+func (r *Registry) RegisterMetadata(map[string]string) {
+ r.registryInfos.Range(func(key, value any) bool {
+ r.Register(value.(*registry.EventMeshRegisterInfo))
+ return true
+ })
+}
+
+func (r *Registry) Register(info *registry.EventMeshRegisterInfo) error {
+ ipPort := strings.Split(info.EndPoint, ":")
+ if len(ipPort) != 2 {
+ return fmt.Errorf("endpoint format err")
+ }
+ ip := ipPort[0]
+ port, err := strconv.ParseInt(ipPort[1], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ _, err = r.client.RegisterInstance(vo.RegisterInstanceParam{
+ Ip: ip,
+ Port: uint64(port),
+ ServiceName: info.EventMeshName,
+ GroupName: uniqGroupName(info.ProtocolType),
+ Healthy: true,
+ Enable: true,
+ Weight: DefaultWeight,
+ })
+ if err != nil {
+ return err
+ }
+ r.registryInfos.Store(info.EventMeshName, info)
+ return nil
+}
+
+func (r *Registry) UnRegister(info *registry.EventMeshUnRegisterInfo) error {
+ ipPort := strings.Split(info.EndPoint, ":")
+ if len(ipPort) != 2 {
+ return fmt.Errorf("endpoint format err")
+ }
+ ip := ipPort[0]
+ port, err := strconv.ParseInt(ipPort[1], 10, 64)
+ if err != nil {
+ return err
+ }
+
+ _, err = r.client.DeregisterInstance(vo.DeregisterInstanceParam{
+ Ip: ip,
+ Port: uint64(port),
+ ServiceName: info.EventMeshName,
+ GroupName: uniqGroupName(info.ProtocolType),
+ })
+ if err != nil {
+ return err
+ }
+ r.registryInfos.Delete(info.EventMeshName)
+ return nil
+}
+
+func uniqGroupName(protoType string) string {
+ return fmt.Sprintf("%s-GROUP", protoType)
+}
diff --git a/plugin/registry/nacos/nacos_test.go b/plugin/registry/nacos/nacos_test.go
new file mode 100644
index 0000000..fa4aff3
--- /dev/null
+++ b/plugin/registry/nacos/nacos_test.go
@@ -0,0 +1,314 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package nacos
+
+import (
+ "fmt"
+ "github.com/nacos-group/nacos-sdk-go/v2/model"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/golang/mock/gomock"
+ "github.com/nacos-group/nacos-sdk-go/v2/vo"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/registry"
+ "github.com/apache/incubator-eventmesh/eventmesh-server-go/plugin/registry/nacos/mocks"
+)
+
+func Test_Init(t *testing.T) {
+ r := &Registry{}
+ assert.NoError(t, r.Init())
+ assert.True(t, r.initStatus.Load())
+ assert.False(t, r.startStatus.Load())
+}
+
+func Test_Start(t *testing.T) {
+ t.Run("create nameing client", func(t *testing.T) {
+ cacheDir := filepath.Join(os.TempDir(), "nacos-test-cache-dir")
+ assert.NoError(t, os.MkdirAll(cacheDir, os.ModePerm))
+ defer os.RemoveAll(cacheDir)
+
+ r := &Registry{
+ cfg: &Config{
+ ServiceName: "test",
+ CacheDir: "/tmp/",
+ Port: "8088",
+ AddressList: "127.0.0.1:8081",
+ },
+ }
+ assert.NoError(t, r.Init())
+ ctrl := gomock.NewController(t)
+ mocks.NewMockINamingClient(ctrl)
+ assert.NoError(t, r.Start())
+ assert.NoError(t, r.Shutdown())
+ assert.False(t, r.initStatus.Load())
+ assert.False(t, r.startStatus.Load())
+ })
+}
+
+func Test_Register(t *testing.T) {
+ cacheDir := filepath.Join(os.TempDir(), "nacos-test-cache-dir")
+ assert.NoError(t, os.MkdirAll(cacheDir, os.ModePerm))
+ defer os.RemoveAll(cacheDir)
+
+ r := &Registry{
+ cfg: &Config{
+ ServiceName: "test",
+ CacheDir: "/tmp/",
+ Port: "8088",
+ AddressList: "127.0.0.1:8081",
+ },
+ }
+ assert.NoError(t, r.Init())
+ assert.True(t, r.initStatus.Load())
+ assert.False(t, r.startStatus.Load())
+ assert.NoError(t, r.Start())
+ ctrl := gomock.NewController(t)
+ nameclient := mocks.NewMockINamingClient(ctrl)
+ meshInfo := ®istry.EventMeshRegisterInfo{
+ EventMeshClusterName: "test",
+ EventMeshName: "test",
+ EndPoint: "127.0.0.1:3333",
+ EventMeshInstanceNumMap: map[string]map[string]int{},
+ Metadata: map[string]string{},
+ ProtocolType: "GRPC",
+ }
+ nameclient.EXPECT().RegisterInstance(vo.RegisterInstanceParam{
+ Ip: "127.0.0.1",
+ Port: 3333,
+ ServiceName: "test",
+ GroupName: uniqGroupName("GRPC"),
+ Healthy: true,
+ Enable: true,
+ Weight: DefaultWeight,
+ }).Return(true, nil).Times(1)
+ r.client = nameclient
+ err := r.Register(meshInfo)
+ assert.NoError(t, err)
+ val, ok := r.registryInfos.Load(meshInfo.EventMeshName)
+ assert.True(t, ok)
+ assert.NotNil(t, val)
+}
+
+func Test_DeRegister(t *testing.T) {
+ cacheDir := filepath.Join(os.TempDir(), "nacos-test-cache-dir")
+ assert.NoError(t, os.MkdirAll(cacheDir, os.ModePerm))
+ defer os.RemoveAll(cacheDir)
+
+ r := &Registry{
+ cfg: &Config{
+ ServiceName: "test",
+ CacheDir: "/tmp/",
+ Port: "8088",
+ AddressList: "127.0.0.1:8081",
+ },
+ }
+ assert.NoError(t, r.Init())
+ assert.True(t, r.initStatus.Load())
+ assert.False(t, r.startStatus.Load())
+ assert.NoError(t, r.Start())
+ ctrl := gomock.NewController(t)
+ nameclient := mocks.NewMockINamingClient(ctrl)
+ meshInfo := ®istry.EventMeshRegisterInfo{
+ EventMeshClusterName: "test",
+ EventMeshName: "test",
+ EndPoint: "127.0.0.1:3333",
+ EventMeshInstanceNumMap: map[string]map[string]int{},
+ Metadata: map[string]string{},
+ ProtocolType: "GRPC",
+ }
+ nameclient.EXPECT().RegisterInstance(vo.RegisterInstanceParam{
+ Ip: "127.0.0.1",
+ Port: 3333,
+ ServiceName: "test",
+ GroupName: uniqGroupName("GRPC"),
+ Healthy: true,
+ Enable: true,
+ Weight: DefaultWeight,
+ }).Return(true, nil).Times(1)
+ r.client = nameclient
+ err := r.Register(meshInfo)
+ assert.NoError(t, err)
+ val, ok := r.registryInfos.Load(meshInfo.EventMeshName)
+ assert.True(t, ok)
+ assert.NotNil(t, val)
+
+ unmeshInfo := ®istry.EventMeshUnRegisterInfo{
+ EventMeshClusterName: "test",
+ EventMeshName: "test",
+ EndPoint: "127.0.0.1:3333",
+ ProtocolType: "GRPC",
+ }
+ nameclient.EXPECT().DeregisterInstance(vo.DeregisterInstanceParam{
+ Ip: "127.0.0.1",
+ Port: 3333,
+ ServiceName: meshInfo.EventMeshName,
+ GroupName: uniqGroupName("GRPC"),
+ }).Return(true, nil).Times(1)
+ err = r.UnRegister(unmeshInfo)
+ assert.NoError(t, err)
+ val, ok = r.registryInfos.Load(meshInfo.EventMeshName)
+ assert.False(t, ok)
+ assert.Nil(t, val)
+}
+
+func Test_FindEventMeshInfoByCluster(t *testing.T) {
+ cacheDir := filepath.Join(os.TempDir(), "nacos-test-cache-dir")
+ assert.NoError(t, os.MkdirAll(cacheDir, os.ModePerm))
+ defer os.RemoveAll(cacheDir)
+
+ r := &Registry{
+ cfg: &Config{
+ ServiceName: "test",
+ CacheDir: "/tmp/",
+ Port: "8088",
+ AddressList: "127.0.0.1:8081",
+ },
+ }
+ assert.NoError(t, r.Init())
+ assert.True(t, r.initStatus.Load())
+ assert.False(t, r.startStatus.Load())
+ assert.NoError(t, r.Start())
+
+ t.Run("return empty", func(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ nameclient := mocks.NewMockINamingClient(ctrl)
+ r.client = nameclient
+ protoList = []string{"GRPC"}
+ nameclient.EXPECT().SelectInstances(vo.SelectInstancesParam{
+ Clusters: []string{"test"},
+ ServiceName: fmt.Sprintf("%v-%v", "eventmesh-server", "GRPC"),
+ GroupName: "1",
+ HealthyOnly: true,
+ }).Return([]model.Instance{}, nil).AnyTimes()
+ val, err := r.FindEventMeshInfoByCluster("test")
+ assert.NoError(t, err)
+ assert.Equal(t, len(val), 0)
+ })
+
+ t.Run("return 1 instance", func(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ nameclient := mocks.NewMockINamingClient(ctrl)
+ r.client = nameclient
+ protoList = []string{"GRPC"}
+ nameclient.EXPECT().SelectInstances(vo.SelectInstancesParam{
+ Clusters: []string{"test"},
+ ServiceName: fmt.Sprintf("%v-%v", "eventmesh-server", "GRPC"),
+ GroupName: "1",
+ HealthyOnly: true,
+ }).Return([]model.Instance{
+ {
+ InstanceId: "1",
+ },
+ }, nil).AnyTimes()
+ val, err := r.FindEventMeshInfoByCluster("test")
+ assert.NoError(t, err)
+ assert.Equal(t, len(val), 1)
+ })
+
+ t.Run("return err", func(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ nameclient := mocks.NewMockINamingClient(ctrl)
+ r.client = nameclient
+ protoList = []string{"GRPC"}
+ mockErr := fmt.Errorf("mock err")
+ nameclient.EXPECT().SelectInstances(vo.SelectInstancesParam{
+ Clusters: []string{"test"},
+ ServiceName: fmt.Sprintf("%v-%v", "eventmesh-server", "GRPC"),
+ GroupName: "1",
+ HealthyOnly: true,
+ }).Return(nil, mockErr).AnyTimes()
+ val, err := r.FindEventMeshInfoByCluster("test")
+ assert.Error(t, err)
+ assert.Equal(t, err, mockErr)
+ assert.Nil(t, val)
+ })
+}
+
+func Test_FindAllEventMeshInfo(t *testing.T) {
+ cacheDir := filepath.Join(os.TempDir(), "nacos-test-cache-dir")
+ assert.NoError(t, os.MkdirAll(cacheDir, os.ModePerm))
+ defer os.RemoveAll(cacheDir)
+
+ r := &Registry{
+ cfg: &Config{
+ ServiceName: "test",
+ CacheDir: "/tmp/",
+ Port: "8088",
+ AddressList: "127.0.0.1:8081",
+ },
+ }
+ assert.NoError(t, r.Init())
+ assert.True(t, r.initStatus.Load())
+ assert.False(t, r.startStatus.Load())
+ assert.NoError(t, r.Start())
+
+ t.Run("return empty", func(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ nameclient := mocks.NewMockINamingClient(ctrl)
+ r.client = nameclient
+ protoList = []string{"GRPC"}
+ nameclient.EXPECT().SelectInstances(vo.SelectInstancesParam{
+ Clusters: []string{},
+ ServiceName: fmt.Sprintf("%v-%v", "eventmesh-server", "GRPC"),
+ GroupName: "GROUP",
+ HealthyOnly: true,
+ }).Return([]model.Instance{}, nil).AnyTimes()
+ val, err := r.FindAllEventMeshInfo()
+ assert.NoError(t, err)
+ assert.Equal(t, len(val), 0)
+ })
+
+ t.Run("return 1 instance", func(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ nameclient := mocks.NewMockINamingClient(ctrl)
+ r.client = nameclient
+ protoList = []string{"GRPC"}
+ nameclient.EXPECT().SelectInstances(vo.SelectInstancesParam{
+ Clusters: []string{},
+ ServiceName: fmt.Sprintf("%v-%v", "eventmesh-server", "GRPC"),
+ GroupName: "GROUP",
+ HealthyOnly: true,
+ }).Return([]model.Instance{
+ {
+ InstanceId: "1",
+ },
+ }, nil).AnyTimes()
+ val, err := r.FindAllEventMeshInfo()
+ assert.NoError(t, err)
+ assert.Equal(t, len(val), 1)
+ })
+
+ t.Run("return err", func(t *testing.T) {
+ ctrl := gomock.NewController(t)
+ nameclient := mocks.NewMockINamingClient(ctrl)
+ r.client = nameclient
+ protoList = []string{"GRPC"}
+ mockErr := fmt.Errorf("mock err")
+ nameclient.EXPECT().SelectInstances(vo.SelectInstancesParam{
+ Clusters: []string{},
+ ServiceName: fmt.Sprintf("%v-%v", "eventmesh-server", "GRPC"),
+ GroupName: "GROUP",
+ HealthyOnly: true,
+ }).Return(nil, mockErr).AnyTimes()
+ val, err := r.FindAllEventMeshInfo()
+ assert.Error(t, err)
+ assert.Equal(t, err, mockErr)
+ assert.Nil(t, val)
+ })
+}
diff --git a/plugin/registry/registry.go b/plugin/registry/registry.go
new file mode 100644
index 0000000..4b6f5d5
--- /dev/null
+++ b/plugin/registry/registry.go
@@ -0,0 +1,30 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+package registry
+
+var Type = "registry"
+
+type Interface interface {
+ Init() error
+ Start() error
+ Shutdown() error
+ FindEventMeshInfoByCluster(clusterName string) ([]*EventMeshDataInfo, error)
+ FindAllEventMeshInfo() ([]*EventMeshDataInfo, error)
+ FindEventMeshClientDistributionData(clusterName, group, purpose string) (map[string]map[string]int, error)
+ RegisterMetadata(map[string]string)
+ Register(info *EventMeshRegisterInfo) error
+ UnRegister(info *EventMeshUnRegisterInfo) error
+}
diff --git a/plugin/setup.go b/plugin/setup.go
new file mode 100644
index 0000000..cfe1468
--- /dev/null
+++ b/plugin/setup.go
@@ -0,0 +1,153 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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.
+
+// This file shows how to load plugins through a yaml config file.
+
+package plugin
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ yaml "gopkg.in/yaml.v3"
+)
+
+var (
+ // SetupTimeout is the timeout for initialization of each plugin.
+ // Modify it if some plugins' initialization does take a long time.
+ SetupTimeout = 10 * time.Second
+
+ // MaxPluginSize is the max number of plugins.
+ MaxPluginSize = 1000
+)
+
+// Config is the configuration of all plugins. plugin type => { plugin name => plugin config }
+type Config map[string]map[string]yaml.Node
+
+// Setup loads plugins by configuration.
+func (c Config) Setup() error {
+ // load plugins one by one through the config file and put them into an ordered plugin queue.
+ plugins, status, err := c.loadPlugins()
+ if err != nil {
+ return err
+ }
+
+ // remove and setup plugins one by one from the front of the ordered plugin queue.
+ if _, err := c.setupPlugins(plugins, status); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (c Config) loadPlugins() (chan pluginInfo, map[string]bool, error) {
+ var (
+ plugins = make(chan pluginInfo, MaxPluginSize) // use channel as plugin queue
+ // plugins' status. plugin key => {true: init done, false: init not done}.
+ status = make(map[string]bool)
+ )
+ for typ, factories := range c {
+ for name, cfg := range factories {
+ factory := Get(typ, name)
+ if factory == nil {
+ return nil, nil, fmt.Errorf("plugin %s:%s no registered or imported, do not configure", typ, name)
+ }
+ p := pluginInfo{
+ factory: factory,
+ typ: typ,
+ name: name,
+ cfg: cfg,
+ }
+ select {
+ case plugins <- p:
+ default:
+ return nil, nil, fmt.Errorf("plugin number exceed max limit:%d", len(plugins))
+ }
+ status[p.key()] = false
+ }
+ }
+ return plugins, status, nil
+}
+
+func (c Config) setupPlugins(plugins chan pluginInfo, status map[string]bool) ([]pluginInfo, error) {
+ var (
+ result []pluginInfo
+ num = len(plugins)
+ )
+ for num > 0 {
+ for i := 0; i < num; i++ {
+ p := <-plugins
+ if err := p.setup(); err != nil {
+ return nil, err
+ }
+ status[p.key()] = true
+ result = append(result, p)
+ }
+ if len(plugins) == num { // none plugin is setup, circular dependency exists.
+ return nil, fmt.Errorf("cycle depends, not plugin is setup")
+ }
+ num = len(plugins) // continue to process plugins that were moved to tail of the channel.
+ }
+ return result, nil
+}
+
+// ------------------------------------------------------------------------------------- //
+
+// pluginInfo is the information of a plugin.
+type pluginInfo struct {
+ factory Plugin
+ typ string
+ name string
+ cfg yaml.Node
+}
+
+// setup initializes a single plugin.
+func (p *pluginInfo) setup() error {
+ var (
+ ch = make(chan struct{})
+ err error
+ )
+ go func() {
+ err = p.factory.Setup(p.name, &YamlNodeDecoder{Node: &p.cfg})
+ close(ch)
+ }()
+ select {
+ case <-ch:
+ case <-time.After(SetupTimeout):
+ return fmt.Errorf("setup plugin %s timeout", p.key())
+ }
+ if err != nil {
+ return fmt.Errorf("setup plugin %s error: %v", p.key(), err)
+ }
+ return nil
+}
+
+// YamlNodeDecoder is a decoder for a yaml.Node of the yaml config file.
+type YamlNodeDecoder struct {
+ Node *yaml.Node
+}
+
+// Decode decodes a yaml.Node of the yaml config file.
+func (d *YamlNodeDecoder) Decode(cfg interface{}) error {
+ if d.Node == nil {
+ return errors.New("yaml node empty")
+ }
+ return d.Node.Decode(cfg)
+}
+
+// key returns the unique index of plugin in the format of 'type-name'.
+func (p *pluginInfo) key() string {
+ return p.typ + "-" + p.name
+}
diff --git a/plugin/types.go b/plugin/types.go
new file mode 100644
index 0000000..14982ed
--- /dev/null
+++ b/plugin/types.go
@@ -0,0 +1,37 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
... 9112 lines suppressed ...
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@eventmesh.apache.org
For additional commands, e-mail: commits-help@eventmesh.apache.org